aboutsummaryrefslogtreecommitdiffstats
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
parente1176e4dfb9018e712d4fa86daf41e9e762a1698 (diff)
downloadkutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.tar.gz
kutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.tar.xz
kutter-581208b2ffeeb2a2128aee0741fa3fd9e46358e2.zip
Run black on all first party python code
-rw-r--r--docs/_klipper3d/mkdocs_hooks.py17
-rw-r--r--klippy/chelper/__init__.py95
-rw-r--r--klippy/clocksync.py212
-rw-r--r--klippy/configfile.py421
-rwxr-xr-xklippy/console.py148
-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
-rw-r--r--klippy/gcode.py263
-rw-r--r--klippy/kinematics/cartesian.py89
-rw-r--r--klippy/kinematics/corexy.py54
-rw-r--r--klippy/kinematics/corexz.py54
-rw-r--r--klippy/kinematics/delta.py283
-rw-r--r--klippy/kinematics/deltesian.py150
-rw-r--r--klippy/kinematics/extruder.py302
-rw-r--r--klippy/kinematics/generic_cartesian.py227
-rw-r--r--klippy/kinematics/hybrid_corexy.py88
-rw-r--r--klippy/kinematics/hybrid_corexz.py88
-rw-r--r--klippy/kinematics/idex_modes.py248
-rw-r--r--klippy/kinematics/kinematic_stepper.py65
-rw-r--r--klippy/kinematics/none.py17
-rw-r--r--klippy/kinematics/polar.py66
-rw-r--r--klippy/kinematics/rotary_delta.py277
-rw-r--r--klippy/kinematics/winch.py31
-rw-r--r--klippy/klippy.py246
-rw-r--r--klippy/mathutil.py53
-rw-r--r--klippy/mcu.py827
-rw-r--r--klippy/msgproto.py215
-rwxr-xr-xklippy/parsedump.py11
-rw-r--r--klippy/pins.py109
-rw-r--r--klippy/queuelogger.py26
-rw-r--r--klippy/reactor.py80
-rw-r--r--klippy/serialhdl.py251
-rw-r--r--klippy/stepper.py367
-rw-r--r--klippy/toolhead.py440
-rw-r--r--klippy/util.py114
-rw-r--r--klippy/webhooks.py194
-rwxr-xr-xscripts/avrsim.py133
-rw-r--r--scripts/buildcommands.py340
-rwxr-xr-xscripts/calibrate_shaper.py281
-rw-r--r--scripts/canbus_query.py52
-rwxr-xr-xscripts/check_whitespace.py31
-rwxr-xr-xscripts/checkstack.py106
-rwxr-xr-xscripts/dump_mcu.py89
-rwxr-xr-xscripts/flash-ar100.py27
-rwxr-xr-xscripts/flash_usb.py143
-rwxr-xr-xscripts/graph_accelerometer.py224
-rwxr-xr-xscripts/graph_extruder.py131
-rwxr-xr-xscripts/graph_mesh.py133
-rwxr-xr-xscripts/graph_motion.py363
-rwxr-xr-xscripts/graph_shaper.py203
-rwxr-xr-xscripts/graph_temp_sensor.py111
-rwxr-xr-xscripts/graphstats.py299
-rwxr-xr-xscripts/logextract.py373
-rw-r--r--scripts/make_version.py16
-rw-r--r--scripts/motan/analyzers.py225
-rwxr-xr-xscripts/motan/data_logger.py88
-rwxr-xr-xscripts/motan/motan_graph.py73
-rw-r--r--scripts/motan/readlog.py413
-rwxr-xr-xscripts/parsecandump.py67
-rw-r--r--scripts/spi_flash/board_defs.py304
-rw-r--r--scripts/spi_flash/fatfs_lib.py17
-rw-r--r--scripts/spi_flash/spi_flash.py917
-rw-r--r--scripts/test_klippy.py115
-rwxr-xr-xscripts/update_chitu.py34
-rwxr-xr-xscripts/update_mks_robin.py40
-rwxr-xr-xscripts/whconsole.py30
202 files changed, 18984 insertions, 12164 deletions
diff --git a/docs/_klipper3d/mkdocs_hooks.py b/docs/_klipper3d/mkdocs_hooks.py
index 396832c4..212d147f 100644
--- a/docs/_klipper3d/mkdocs_hooks.py
+++ b/docs/_klipper3d/mkdocs_hooks.py
@@ -12,7 +12,8 @@ import logging
# 3. Remove leading spaces from top-level lists so that those lists
# are rendered correctly.
-logger = logging.getLogger('mkdocs.mkdocs_hooks.transform')
+logger = logging.getLogger("mkdocs.mkdocs_hooks.transform")
+
def transform(markdown: str, page, config, files):
in_code_block = 0
@@ -20,11 +21,9 @@ def transform(markdown: str, page, config, files):
lines = markdown.splitlines()
for i in range(len(lines)):
line_out = lines[i]
- in_code_block = (in_code_block +
- len(re.findall("\s*[`]{3,}", line_out))) % 2
+ in_code_block = (in_code_block + len(re.findall("\s*[`]{3,}", line_out))) % 2
if not in_code_block:
- line_out = line_out.replace('](../',
- f"]({config['repo_url']}blob/master/")
+ line_out = line_out.replace("](../", f"]({config['repo_url']}blob/master/")
line_out = re.sub("\\\s*$", "<br>", line_out)
# check that lists at level 0 are not indented
# (no space before *|-|1.)
@@ -35,8 +34,12 @@ def transform(markdown: str, page, config, files):
if not in_list:
line_out = re.sub(r"^\s+(\*|-|\d+\.) ", r"\1 ", line_out)
if line_out != lines[i]:
- logger.debug((f'[mkdocs_hooks] rewrite line {i+1}: '
- f'"{lines[i]}" -> "{line_out}"'))
+ logger.debug(
+ (
+ f"[mkdocs_hooks] rewrite line {i+1}: "
+ f'"{lines[i]}" -> "{line_out}"'
+ )
+ )
lines[i] = line_out
output = "\n".join(lines)
return output
diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py
index 2aed107a..0a40dfeb 100644
--- a/klippy/chelper/__init__.py
+++ b/klippy/chelper/__init__.py
@@ -12,21 +12,44 @@ import cffi
######################################################################
GCC_CMD = "gcc"
-COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
- " -flto -fwhole-program -fno-use-linker-plugin"
- " -o %s %s")
+COMPILE_ARGS = (
+ "-Wall -g -O2 -shared -fPIC"
+ " -flto -fwhole-program -fno-use-linker-plugin"
+ " -o %s %s"
+)
SSE_FLAGS = "-mfpmath=sse -msse2"
SOURCE_FILES = [
- 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c',
- 'pollreactor.c', 'msgblock.c', 'trdispatch.c',
- 'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
- 'kin_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c',
- 'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c'
+ "pyhelper.c",
+ "serialqueue.c",
+ "stepcompress.c",
+ "itersolve.c",
+ "trapq.c",
+ "pollreactor.c",
+ "msgblock.c",
+ "trdispatch.c",
+ "kin_cartesian.c",
+ "kin_corexy.c",
+ "kin_corexz.c",
+ "kin_delta.c",
+ "kin_deltesian.c",
+ "kin_polar.c",
+ "kin_rotary_delta.c",
+ "kin_winch.c",
+ "kin_extruder.c",
+ "kin_shaper.c",
+ "kin_idex.c",
+ "kin_generic.c",
]
DEST_LIB = "c_helper.so"
OTHER_FILES = [
- 'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h',
- 'trapq.h', 'pollreactor.h', 'msgblock.h'
+ "list.h",
+ "serialqueue.h",
+ "stepcompress.h",
+ "itersolve.h",
+ "pyhelper.h",
+ "trapq.h",
+ "pollreactor.h",
+ "msgblock.h",
]
defs_stepcompress = """
@@ -226,18 +249,33 @@ defs_std = """
"""
defs_all = [
- defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress,
- defs_itersolve, defs_trapq, defs_trdispatch,
- defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta,
- defs_kin_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch,
- defs_kin_extruder, defs_kin_shaper, defs_kin_idex,
+ defs_pyhelper,
+ defs_serialqueue,
+ defs_std,
+ defs_stepcompress,
+ defs_itersolve,
+ defs_trapq,
+ defs_trdispatch,
+ defs_kin_cartesian,
+ defs_kin_corexy,
+ defs_kin_corexz,
+ defs_kin_delta,
+ defs_kin_deltesian,
+ defs_kin_polar,
+ defs_kin_rotary_delta,
+ defs_kin_winch,
+ defs_kin_extruder,
+ defs_kin_shaper,
+ defs_kin_idex,
defs_kin_generic_cartesian,
]
+
# Update filenames to an absolute path
def get_abs_files(srcdir, filelist):
return [os.path.join(srcdir, fname) for fname in filelist]
+
# Return the list of file modification times
def get_mtimes(filelist):
out = []
@@ -249,19 +287,21 @@ def get_mtimes(filelist):
out.append(t)
return out
+
# Check if the code needs to be compiled
def check_build_code(sources, target):
src_times = get_mtimes(sources)
obj_times = get_mtimes([target])
return not obj_times or max(src_times) > min(obj_times)
+
# Check if the current gcc version supports a particular command-line option
def check_gcc_option(option):
- cmd = "%s %s -S -o /dev/null -xc /dev/null > /dev/null 2>&1" % (
- GCC_CMD, option)
+ cmd = "%s %s -S -o /dev/null -xc /dev/null > /dev/null 2>&1" % (GCC_CMD, option)
res = os.system(cmd)
return res == 0
+
# Check if the current gcc version supports a particular command-line option
def do_build_code(cmd):
res = os.system(cmd)
@@ -270,14 +310,17 @@ def do_build_code(cmd):
logging.error(msg)
raise Exception(msg)
+
FFI_main = None
FFI_lib = None
pyhelper_logging_callback = None
+
# Helper invoked from C errorf() code to log errors
def logging_callback(msg):
logging.error(FFI_main.string(msg))
+
# Return the Foreign Function Interface api to the caller
def get_ffi():
global FFI_main, FFI_lib, pyhelper_logging_callback
@@ -286,20 +329,21 @@ def get_ffi():
srcfiles = get_abs_files(srcdir, SOURCE_FILES)
ofiles = get_abs_files(srcdir, OTHER_FILES)
destlib = get_abs_files(srcdir, [DEST_LIB])[0]
- if check_build_code(srcfiles+ofiles+[__file__], destlib):
+ if check_build_code(srcfiles + ofiles + [__file__], destlib):
if check_gcc_option(SSE_FLAGS):
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
else:
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
logging.info("Building C code module %s", DEST_LIB)
- do_build_code(cmd % (destlib, ' '.join(srcfiles)))
+ do_build_code(cmd % (destlib, " ".join(srcfiles)))
FFI_main = cffi.FFI()
for d in defs_all:
FFI_main.cdef(d)
FFI_lib = FFI_main.dlopen(destlib)
# Setup error logging
- pyhelper_logging_callback = FFI_main.callback("void func(const char *)",
- logging_callback)
+ pyhelper_logging_callback = FFI_main.callback(
+ "void func(const char *)", logging_callback
+ )
FFI_lib.set_python_logging_callback(pyhelper_logging_callback)
return FFI_main, FFI_lib
@@ -309,11 +353,12 @@ def get_ffi():
######################################################################
HC_COMPILE_CMD = "gcc -Wall -g -O2 -o %s %s -lusb"
-HC_SOURCE_FILES = ['hub-ctrl.c']
-HC_SOURCE_DIR = '../../lib/hub-ctrl'
+HC_SOURCE_FILES = ["hub-ctrl.c"]
+HC_SOURCE_DIR = "../../lib/hub-ctrl"
HC_TARGET = "hub-ctrl"
HC_CMD = "sudo %s/hub-ctrl -h 0 -P 2 -p %d"
+
def run_hub_ctrl(enable_power):
srcdir = os.path.dirname(os.path.realpath(__file__))
hubdir = os.path.join(srcdir, HC_SOURCE_DIR)
@@ -321,9 +366,9 @@ def run_hub_ctrl(enable_power):
destlib = get_abs_files(hubdir, [HC_TARGET])[0]
if check_build_code(srcfiles, destlib):
logging.info("Building C code module %s", HC_TARGET)
- do_build_code(HC_COMPILE_CMD % (destlib, ' '.join(srcfiles)))
+ do_build_code(HC_COMPILE_CMD % (destlib, " ".join(srcfiles)))
os.system(HC_CMD % (hubdir, enable_power))
-if __name__ == '__main__':
+if __name__ == "__main__":
get_ffi()
diff --git a/klippy/clocksync.py b/klippy/clocksync.py
index 80ed9db6..1e444381 100644
--- a/klippy/clocksync.py
+++ b/klippy/clocksync.py
@@ -5,9 +5,10 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, math
-RTT_AGE = .000010 / (60. * 60.)
-DECAY = 1. / 30.
-TRANSMIT_EXTRA = .001
+RTT_AGE = 0.000010 / (60.0 * 60.0)
+DECAY = 1.0 / 30.0
+TRANSMIT_EXTRA = 0.001
+
class ClockSync:
def __init__(self, reactor):
@@ -16,147 +17,190 @@ class ClockSync:
self.get_clock_timer = reactor.register_timer(self._get_clock_event)
self.get_clock_cmd = self.cmd_queue = None
self.queries_pending = 0
- self.mcu_freq = 1.
+ self.mcu_freq = 1.0
self.last_clock = 0
- self.clock_est = (0., 0., 0.)
+ self.clock_est = (0.0, 0.0, 0.0)
# Minimum round-trip-time tracking
self.min_half_rtt = 999999999.9
- self.min_rtt_time = 0.
+ self.min_rtt_time = 0.0
# Linear regression of mcu clock and system sent_time
- self.time_avg = self.time_variance = 0.
- self.clock_avg = self.clock_covariance = 0.
- self.prediction_variance = 0.
- self.last_prediction_time = 0.
+ self.time_avg = self.time_variance = 0.0
+ self.clock_avg = self.clock_covariance = 0.0
+ self.prediction_variance = 0.0
+ self.last_prediction_time = 0.0
+
def connect(self, serial):
self.serial = serial
- self.mcu_freq = serial.msgparser.get_constant_float('CLOCK_FREQ')
+ self.mcu_freq = serial.msgparser.get_constant_float("CLOCK_FREQ")
# Load initial clock and frequency
- params = serial.send_with_response('get_uptime', 'uptime')
- self.last_clock = (params['high'] << 32) | params['clock']
+ params = serial.send_with_response("get_uptime", "uptime")
+ self.last_clock = (params["high"] << 32) | params["clock"]
self.clock_avg = self.last_clock
- self.time_avg = params['#sent_time']
+ self.time_avg = params["#sent_time"]
self.clock_est = (self.time_avg, self.clock_avg, self.mcu_freq)
- self.prediction_variance = (.001 * self.mcu_freq)**2
+ self.prediction_variance = (0.001 * self.mcu_freq) ** 2
# Enable periodic get_clock timer
for i in range(8):
self.reactor.pause(self.reactor.monotonic() + 0.050)
- self.last_prediction_time = -9999.
- params = serial.send_with_response('get_clock', 'clock')
+ self.last_prediction_time = -9999.0
+ params = serial.send_with_response("get_clock", "clock")
self._handle_clock(params)
- self.get_clock_cmd = serial.get_msgparser().create_command('get_clock')
+ self.get_clock_cmd = serial.get_msgparser().create_command("get_clock")
self.cmd_queue = serial.alloc_command_queue()
- serial.register_response(self._handle_clock, 'clock')
+ serial.register_response(self._handle_clock, "clock")
self.reactor.update_timer(self.get_clock_timer, self.reactor.NOW)
+
def connect_file(self, serial, pace=False):
self.serial = serial
- self.mcu_freq = serial.msgparser.get_constant_float('CLOCK_FREQ')
- self.clock_est = (0., 0., self.mcu_freq)
- freq = 1000000000000.
+ self.mcu_freq = serial.msgparser.get_constant_float("CLOCK_FREQ")
+ self.clock_est = (0.0, 0.0, self.mcu_freq)
+ freq = 1000000000000.0
if pace:
freq = self.mcu_freq
serial.set_clock_est(freq, self.reactor.monotonic(), 0, 0)
+
# MCU clock querying (_handle_clock is invoked from background thread)
def _get_clock_event(self, eventtime):
self.serial.raw_send(self.get_clock_cmd, 0, 0, self.cmd_queue)
self.queries_pending += 1
# Use an unusual time for the next event so clock messages
# don't resonate with other periodic events.
- return eventtime + .9839
+ return eventtime + 0.9839
+
def _handle_clock(self, params):
self.queries_pending = 0
# Extend clock to 64bit
last_clock = self.last_clock
- clock_delta = (params['clock'] - last_clock) & 0xffffffff
+ clock_delta = (params["clock"] - last_clock) & 0xFFFFFFFF
self.last_clock = clock = last_clock + clock_delta
# Check if this is the best round-trip-time seen so far
- sent_time = params['#sent_time']
+ sent_time = params["#sent_time"]
if not sent_time:
return
- receive_time = params['#receive_time']
- half_rtt = .5 * (receive_time - sent_time)
+ receive_time = params["#receive_time"]
+ half_rtt = 0.5 * (receive_time - sent_time)
aged_rtt = (sent_time - self.min_rtt_time) * RTT_AGE
if half_rtt < self.min_half_rtt + aged_rtt:
self.min_half_rtt = half_rtt
self.min_rtt_time = sent_time
- logging.debug("new minimum rtt %.3f: hrtt=%.6f freq=%d",
- sent_time, half_rtt, self.clock_est[2])
+ logging.debug(
+ "new minimum rtt %.3f: hrtt=%.6f freq=%d",
+ sent_time,
+ half_rtt,
+ self.clock_est[2],
+ )
# Filter out samples that are extreme outliers
- exp_clock = ((sent_time - self.time_avg) * self.clock_est[2]
- + self.clock_avg)
- clock_diff2 = (clock - exp_clock)**2
- if (clock_diff2 > 25. * self.prediction_variance
- and clock_diff2 > (.000500 * self.mcu_freq)**2):
- if clock > exp_clock and sent_time < self.last_prediction_time+10.:
- logging.debug("Ignoring clock sample %.3f:"
- " freq=%d diff=%d stddev=%.3f",
- sent_time, self.clock_est[2], clock - exp_clock,
- math.sqrt(self.prediction_variance))
+ exp_clock = (sent_time - self.time_avg) * self.clock_est[2] + self.clock_avg
+ clock_diff2 = (clock - exp_clock) ** 2
+ if (
+ clock_diff2 > 25.0 * self.prediction_variance
+ and clock_diff2 > (0.000500 * self.mcu_freq) ** 2
+ ):
+ if clock > exp_clock and sent_time < self.last_prediction_time + 10.0:
+ logging.debug(
+ "Ignoring clock sample %.3f:" " freq=%d diff=%d stddev=%.3f",
+ sent_time,
+ self.clock_est[2],
+ clock - exp_clock,
+ math.sqrt(self.prediction_variance),
+ )
return
- logging.info("Resetting prediction variance %.3f:"
- " freq=%d diff=%d stddev=%.3f",
- sent_time, self.clock_est[2], clock - exp_clock,
- math.sqrt(self.prediction_variance))
- self.prediction_variance = (.001 * self.mcu_freq)**2
+ logging.info(
+ "Resetting prediction variance %.3f:" " freq=%d diff=%d stddev=%.3f",
+ sent_time,
+ self.clock_est[2],
+ clock - exp_clock,
+ math.sqrt(self.prediction_variance),
+ )
+ self.prediction_variance = (0.001 * self.mcu_freq) ** 2
else:
self.last_prediction_time = sent_time
- self.prediction_variance = (
- (1. - DECAY) * (self.prediction_variance + clock_diff2 * DECAY))
+ self.prediction_variance = (1.0 - DECAY) * (
+ self.prediction_variance + clock_diff2 * DECAY
+ )
# Add clock and sent_time to linear regression
diff_sent_time = sent_time - self.time_avg
self.time_avg += DECAY * diff_sent_time
- self.time_variance = (1. - DECAY) * (
- self.time_variance + diff_sent_time**2 * DECAY)
+ self.time_variance = (1.0 - DECAY) * (
+ self.time_variance + diff_sent_time**2 * DECAY
+ )
diff_clock = clock - self.clock_avg
self.clock_avg += DECAY * diff_clock
- self.clock_covariance = (1. - DECAY) * (
- self.clock_covariance + diff_sent_time * diff_clock * DECAY)
+ self.clock_covariance = (1.0 - DECAY) * (
+ self.clock_covariance + diff_sent_time * diff_clock * DECAY
+ )
# Update prediction from linear regression
new_freq = self.clock_covariance / self.time_variance
pred_stddev = math.sqrt(self.prediction_variance)
- self.serial.set_clock_est(new_freq, self.time_avg + TRANSMIT_EXTRA,
- int(self.clock_avg - 3. * pred_stddev), clock)
- self.clock_est = (self.time_avg + self.min_half_rtt,
- self.clock_avg, new_freq)
- #logging.debug("regr %.3f: freq=%.3f d=%d(%.3f)",
+ self.serial.set_clock_est(
+ new_freq,
+ self.time_avg + TRANSMIT_EXTRA,
+ int(self.clock_avg - 3.0 * pred_stddev),
+ clock,
+ )
+ self.clock_est = (self.time_avg + self.min_half_rtt, self.clock_avg, new_freq)
+ # logging.debug("regr %.3f: freq=%.3f d=%d(%.3f)",
# sent_time, new_freq, clock - exp_clock, pred_stddev)
+
# clock frequency conversions
def print_time_to_clock(self, print_time):
return int(print_time * self.mcu_freq)
+
def clock_to_print_time(self, clock):
return clock / self.mcu_freq
+
# system time conversions
def get_clock(self, eventtime):
sample_time, clock, freq = self.clock_est
return int(clock + (eventtime - sample_time) * freq)
+
def estimate_clock_systime(self, reqclock):
sample_time, clock, freq = self.clock_est
- return float(reqclock - clock)/freq + sample_time
+ return float(reqclock - clock) / freq + sample_time
+
def estimated_print_time(self, eventtime):
return self.clock_to_print_time(self.get_clock(eventtime))
+
# misc commands
def clock32_to_clock64(self, clock32):
last_clock = self.last_clock
- clock_diff = (clock32 - last_clock) & 0xffffffff
+ clock_diff = (clock32 - last_clock) & 0xFFFFFFFF
clock_diff -= (clock_diff & 0x80000000) << 1
return last_clock + clock_diff
+
def is_active(self):
return self.queries_pending <= 4
+
def dump_debug(self):
sample_time, clock, freq = self.clock_est
- return ("clocksync state: mcu_freq=%d last_clock=%d"
- " clock_est=(%.3f %d %.3f) min_half_rtt=%.6f min_rtt_time=%.3f"
- " time_avg=%.3f(%.3f) clock_avg=%.3f(%.3f)"
- " pred_variance=%.3f" % (
- self.mcu_freq, self.last_clock, sample_time, clock, freq,
- self.min_half_rtt, self.min_rtt_time,
- self.time_avg, self.time_variance,
- self.clock_avg, self.clock_covariance,
- self.prediction_variance))
+ return (
+ "clocksync state: mcu_freq=%d last_clock=%d"
+ " clock_est=(%.3f %d %.3f) min_half_rtt=%.6f min_rtt_time=%.3f"
+ " time_avg=%.3f(%.3f) clock_avg=%.3f(%.3f)"
+ " pred_variance=%.3f"
+ % (
+ self.mcu_freq,
+ self.last_clock,
+ sample_time,
+ clock,
+ freq,
+ self.min_half_rtt,
+ self.min_rtt_time,
+ self.time_avg,
+ self.time_variance,
+ self.clock_avg,
+ self.clock_covariance,
+ self.prediction_variance,
+ )
+ )
+
def stats(self, eventtime):
sample_time, clock, freq = self.clock_est
return "freq=%d" % (freq,)
+
def calibrate_clock(self, print_time, eventtime):
- return (0., self.mcu_freq)
+ return (0.0, self.mcu_freq)
+
# Clock syncing code for secondary MCUs (whose clocks are sync'ed to a
# primary MCU)
@@ -164,34 +208,44 @@ class SecondarySync(ClockSync):
def __init__(self, reactor, main_sync):
ClockSync.__init__(self, reactor)
self.main_sync = main_sync
- self.clock_adj = (0., 1.)
- self.last_sync_time = 0.
+ self.clock_adj = (0.0, 1.0)
+ self.last_sync_time = 0.0
+
def connect(self, serial):
ClockSync.connect(self, serial)
- self.clock_adj = (0., self.mcu_freq)
+ self.clock_adj = (0.0, self.mcu_freq)
curtime = self.reactor.monotonic()
main_print_time = self.main_sync.estimated_print_time(curtime)
local_print_time = self.estimated_print_time(curtime)
self.clock_adj = (main_print_time - local_print_time, self.mcu_freq)
- self.calibrate_clock(0., curtime)
+ self.calibrate_clock(0.0, curtime)
+
def connect_file(self, serial, pace=False):
ClockSync.connect_file(self, serial, pace)
- self.clock_adj = (0., self.mcu_freq)
+ self.clock_adj = (0.0, self.mcu_freq)
+
# clock frequency conversions
def print_time_to_clock(self, print_time):
adjusted_offset, adjusted_freq = self.clock_adj
return int((print_time - adjusted_offset) * adjusted_freq)
+
def clock_to_print_time(self, clock):
adjusted_offset, adjusted_freq = self.clock_adj
return clock / adjusted_freq + adjusted_offset
+
# misc commands
def dump_debug(self):
adjusted_offset, adjusted_freq = self.clock_adj
return "%s clock_adj=(%.3f %.3f)" % (
- ClockSync.dump_debug(self), adjusted_offset, adjusted_freq)
+ ClockSync.dump_debug(self),
+ adjusted_offset,
+ adjusted_freq,
+ )
+
def stats(self, eventtime):
adjusted_offset, adjusted_freq = self.clock_adj
return "%s adj=%d" % (ClockSync.stats(self, eventtime), adjusted_freq)
+
def calibrate_clock(self, print_time, eventtime):
# Calculate: est_print_time = main_sync.estimatated_print_time()
ser_time, ser_clock, ser_freq = self.main_sync.clock_est
@@ -200,16 +254,20 @@ class SecondarySync(ClockSync):
est_print_time = est_main_clock / main_mcu_freq
# Determine sync1_print_time and sync2_print_time
sync1_print_time = max(print_time, est_print_time)
- sync2_print_time = max(sync1_print_time + 4., self.last_sync_time,
- print_time + 2.5 * (print_time - est_print_time))
+ sync2_print_time = max(
+ sync1_print_time + 4.0,
+ self.last_sync_time,
+ print_time + 2.5 * (print_time - est_print_time),
+ )
# Calc sync2_sys_time (inverse of main_sync.estimatated_print_time)
sync2_main_clock = sync2_print_time * main_mcu_freq
sync2_sys_time = ser_time + (sync2_main_clock - ser_clock) / ser_freq
# Adjust freq so estimated print_time will match at sync2_print_time
sync1_clock = self.print_time_to_clock(sync1_print_time)
sync2_clock = self.get_clock(sync2_sys_time)
- adjusted_freq = ((sync2_clock - sync1_clock)
- / (sync2_print_time - sync1_print_time))
+ adjusted_freq = (sync2_clock - sync1_clock) / (
+ sync2_print_time - sync1_print_time
+ )
adjusted_offset = sync1_print_time - sync1_clock / adjusted_freq
# Apply new values
self.clock_adj = (adjusted_offset, adjusted_freq)
diff --git a/klippy/configfile.py b/klippy/configfile.py
index 8210de2b..3ddfe0d3 100644
--- a/klippy/configfile.py
+++ b/klippy/configfile.py
@@ -12,67 +12,121 @@ error = configparser.Error
# Config section parsing helper
######################################################################
+
class sentinel:
pass
+
class ConfigWrapper:
error = configparser.Error
+
def __init__(self, printer, fileconfig, access_tracking, section):
self.printer = printer
self.fileconfig = fileconfig
self.access_tracking = access_tracking
self.section = section
+
def get_printer(self):
return self.printer
+
def get_name(self):
return self.section
- def _get_wrapper(self, parser, option, default, minval=None, maxval=None,
- above=None, below=None, note_valid=True):
+
+ def _get_wrapper(
+ self,
+ parser,
+ option,
+ default,
+ minval=None,
+ maxval=None,
+ above=None,
+ below=None,
+ note_valid=True,
+ ):
if not self.fileconfig.has_option(self.section, option):
if default is not sentinel:
if note_valid and default is not None:
acc_id = (self.section.lower(), option.lower())
self.access_tracking[acc_id] = default
return default
- raise error("Option '%s' in section '%s' must be specified"
- % (option, self.section))
+ raise error(
+ "Option '%s' in section '%s' must be specified" % (option, self.section)
+ )
try:
v = parser(self.section, option)
except self.error as e:
raise
except:
- raise error("Unable to parse option '%s' in section '%s'"
- % (option, self.section))
+ raise error(
+ "Unable to parse option '%s' in section '%s'" % (option, self.section)
+ )
if note_valid:
self.access_tracking[(self.section.lower(), option.lower())] = v
if minval is not None and v < minval:
- raise error("Option '%s' in section '%s' must have minimum of %s"
- % (option, self.section, minval))
+ raise error(
+ "Option '%s' in section '%s' must have minimum of %s"
+ % (option, self.section, minval)
+ )
if maxval is not None and v > maxval:
- raise error("Option '%s' in section '%s' must have maximum of %s"
- % (option, self.section, maxval))
+ raise error(
+ "Option '%s' in section '%s' must have maximum of %s"
+ % (option, self.section, maxval)
+ )
if above is not None and v <= above:
- raise error("Option '%s' in section '%s' must be above %s"
- % (option, self.section, above))
+ raise error(
+ "Option '%s' in section '%s' must be above %s"
+ % (option, self.section, above)
+ )
if below is not None and v >= below:
- raise self.error("Option '%s' in section '%s' must be below %s"
- % (option, self.section, below))
+ raise self.error(
+ "Option '%s' in section '%s' must be below %s"
+ % (option, self.section, below)
+ )
return v
+
def get(self, option, default=sentinel, note_valid=True):
- return self._get_wrapper(self.fileconfig.get, option, default,
- note_valid=note_valid)
- def getint(self, option, default=sentinel, minval=None, maxval=None,
- note_valid=True):
- return self._get_wrapper(self.fileconfig.getint, option, default,
- minval, maxval, note_valid=note_valid)
- def getfloat(self, option, default=sentinel, minval=None, maxval=None,
- above=None, below=None, note_valid=True):
- return self._get_wrapper(self.fileconfig.getfloat, option, default,
- minval, maxval, above, below,
- note_valid=note_valid)
+ return self._get_wrapper(
+ self.fileconfig.get, option, default, note_valid=note_valid
+ )
+
+ def getint(
+ self, option, default=sentinel, minval=None, maxval=None, note_valid=True
+ ):
+ return self._get_wrapper(
+ self.fileconfig.getint,
+ option,
+ default,
+ minval,
+ maxval,
+ note_valid=note_valid,
+ )
+
+ def getfloat(
+ self,
+ option,
+ default=sentinel,
+ minval=None,
+ maxval=None,
+ above=None,
+ below=None,
+ note_valid=True,
+ ):
+ return self._get_wrapper(
+ self.fileconfig.getfloat,
+ option,
+ default,
+ minval,
+ maxval,
+ above,
+ below,
+ note_valid=note_valid,
+ )
+
def getboolean(self, option, default=sentinel, note_valid=True):
- return self._get_wrapper(self.fileconfig.getboolean, option, default,
- note_valid=note_valid)
+ return self._get_wrapper(
+ self.fileconfig.getboolean, option, default, note_valid=note_valid
+ )
+
def getchoice(self, option, choices, default=sentinel, note_valid=True):
if type(choices) == type([]):
choices = {i: i for i in choices}
@@ -81,11 +135,21 @@ class ConfigWrapper:
else:
c = self.get(option, default, note_valid=note_valid)
if c not in choices:
- raise error("Choice '%s' for option '%s' in section '%s'"
- " is not a valid choice" % (c, option, self.section))
+ raise error(
+ "Choice '%s' for option '%s' in section '%s'"
+ " is not a valid choice" % (c, option, self.section)
+ )
return choices[c]
- def getlists(self, option, default=sentinel, seps=(',',), count=None,
- parser=str, note_valid=True):
+
+ def getlists(
+ self,
+ option,
+ default=sentinel,
+ seps=(",",),
+ count=None,
+ parser=str,
+ note_valid=True,
+ ):
def lparser(value, pos):
if len(value.strip()) == 0:
# Return an empty list instead of [''] for empty string
@@ -97,45 +161,72 @@ class ConfigWrapper:
return tuple([lparser(p, pos - 1) for p in parts if p])
res = [parser(p) for p in parts]
if count is not None and len(res) != count:
- raise error("Option '%s' in section '%s' must have %d elements"
- % (option, self.section, count))
+ raise error(
+ "Option '%s' in section '%s' must have %d elements"
+ % (option, self.section, count)
+ )
return tuple(res)
+
def fcparser(section, option):
return lparser(self.fileconfig.get(section, option), len(seps) - 1)
- return self._get_wrapper(fcparser, option, default,
- note_valid=note_valid)
- def getlist(self, option, default=sentinel, sep=',', count=None,
- note_valid=True):
- return self.getlists(option, default, seps=(sep,), count=count,
- parser=str, note_valid=note_valid)
- def getintlist(self, option, default=sentinel, sep=',', count=None,
- note_valid=True):
- return self.getlists(option, default, seps=(sep,), count=count,
- parser=int, note_valid=note_valid)
- def getfloatlist(self, option, default=sentinel, sep=',', count=None,
- note_valid=True):
- return self.getlists(option, default, seps=(sep,), count=count,
- parser=float, note_valid=note_valid)
+
+ return self._get_wrapper(fcparser, option, default, note_valid=note_valid)
+
+ def getlist(self, option, default=sentinel, sep=",", count=None, note_valid=True):
+ return self.getlists(
+ option, default, seps=(sep,), count=count, parser=str, note_valid=note_valid
+ )
+
+ def getintlist(
+ self, option, default=sentinel, sep=",", count=None, note_valid=True
+ ):
+ return self.getlists(
+ option, default, seps=(sep,), count=count, parser=int, note_valid=note_valid
+ )
+
+ def getfloatlist(
+ self, option, default=sentinel, sep=",", count=None, note_valid=True
+ ):
+ return self.getlists(
+ option,
+ default,
+ seps=(sep,),
+ count=count,
+ parser=float,
+ note_valid=note_valid,
+ )
+
def getsection(self, section):
- return ConfigWrapper(self.printer, self.fileconfig,
- self.access_tracking, section)
+ return ConfigWrapper(
+ self.printer, self.fileconfig, self.access_tracking, section
+ )
+
def has_section(self, section):
return self.fileconfig.has_section(section)
+
def get_prefix_sections(self, prefix):
- return [self.getsection(s) for s in self.fileconfig.sections()
- if s.startswith(prefix)]
+ return [
+ self.getsection(s)
+ for s in self.fileconfig.sections()
+ if s.startswith(prefix)
+ ]
+
def get_prefix_options(self, prefix):
- return [o for o in self.fileconfig.options(self.section)
- if o.startswith(prefix)]
+ return [
+ o for o in self.fileconfig.options(self.section) if o.startswith(prefix)
+ ]
+
def deprecate(self, option, value=None):
if not self.fileconfig.has_option(self.section, option):
return
if value is None:
- msg = ("Option '%s' in section '%s' is deprecated."
- % (option, self.section))
+ msg = "Option '%s' in section '%s' is deprecated." % (option, self.section)
else:
- msg = ("Value '%s' in option '%s' in section '%s' is deprecated."
- % (value, option, self.section))
+ msg = "Value '%s' in option '%s' in section '%s' is deprecated." % (
+ value,
+ option,
+ self.section,
+ )
pconfig = self.printer.lookup_object("configfile")
pconfig.deprecate(self.section, option, value, msg)
@@ -144,48 +235,54 @@ class ConfigWrapper:
# Config file parsing (with include file support)
######################################################################
+
class ConfigFileReader:
def read_config_file(self, filename):
try:
- f = open(filename, 'r')
+ f = open(filename, "r")
data = f.read()
f.close()
except:
msg = "Unable to open config file %s" % (filename,)
logging.exception(msg)
raise error(msg)
- return data.replace('\r\n', '\n')
+ return data.replace("\r\n", "\n")
+
def build_config_string(self, fileconfig):
sfile = io.StringIO()
fileconfig.write(sfile)
return sfile.getvalue().strip()
+
def append_fileconfig(self, fileconfig, data, filename):
if not data:
return
# Strip trailing comments
- lines = data.split('\n')
+ lines = data.split("\n")
for i, line in enumerate(lines):
- pos = line.find('#')
+ pos = line.find("#")
if pos >= 0:
lines[i] = line[:pos]
- sbuffer = io.StringIO('\n'.join(lines))
+ sbuffer = io.StringIO("\n".join(lines))
if sys.version_info.major >= 3:
fileconfig.read_file(sbuffer, filename)
else:
fileconfig.readfp(sbuffer, filename)
+
def _create_fileconfig(self):
if sys.version_info.major >= 3:
fileconfig = configparser.RawConfigParser(
- strict=False, inline_comment_prefixes=(';', '#'))
+ strict=False, inline_comment_prefixes=(";", "#")
+ )
else:
fileconfig = configparser.RawConfigParser()
return fileconfig
+
def build_fileconfig(self, data, filename):
fileconfig = self._create_fileconfig()
self.append_fileconfig(fileconfig, data, filename)
return fileconfig
- def _resolve_include(self, source_filename, include_spec, fileconfig,
- visited):
+
+ def _resolve_include(self, source_filename, include_spec, fileconfig, visited):
dirname = os.path.dirname(source_filename)
include_spec = include_spec.strip()
include_glob = os.path.join(dirname, include_spec)
@@ -196,36 +293,36 @@ class ConfigFileReader:
include_filenames.sort()
for include_filename in include_filenames:
include_data = self.read_config_file(include_filename)
- self._parse_config(include_data, include_filename, fileconfig,
- visited)
+ self._parse_config(include_data, include_filename, fileconfig, visited)
return include_filenames
+
def _parse_config(self, data, filename, fileconfig, visited):
path = os.path.abspath(filename)
if path in visited:
raise error("Recursive include of config file '%s'" % (filename))
visited.add(path)
- lines = data.split('\n')
+ lines = data.split("\n")
# Buffer lines between includes and parse as a unit so that overrides
# in includes apply linearly as they do within a single file
buf = []
for line in lines:
# Strip trailing comment
- pos = line.find('#')
+ pos = line.find("#")
if pos >= 0:
line = line[:pos]
# Process include or buffer line
mo = configparser.RawConfigParser.SECTCRE.match(line)
- header = mo and mo.group('header')
- if header and header.startswith('include '):
- self.append_fileconfig(fileconfig, '\n'.join(buf), filename)
+ header = mo and mo.group("header")
+ if header and header.startswith("include "):
+ self.append_fileconfig(fileconfig, "\n".join(buf), filename)
del buf[:]
include_spec = header[8:].strip()
- self._resolve_include(filename, include_spec, fileconfig,
- visited)
+ self._resolve_include(filename, include_spec, fileconfig, visited)
else:
buf.append(line)
- self.append_fileconfig(fileconfig, '\n'.join(buf), filename)
+ self.append_fileconfig(fileconfig, "\n".join(buf), filename)
visited.remove(path)
+
def build_fileconfig_with_includes(self, data, filename):
fileconfig = self._create_fileconfig()
self._parse_config(data, filename, fileconfig, set())
@@ -242,78 +339,91 @@ AUTOSAVE_HEADER = """
#*#
"""
+
class ConfigAutoSave:
def __init__(self, printer):
self.printer = printer
self.fileconfig = None
self.status_save_pending = {}
self.save_config_pending = False
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG,
- desc=self.cmd_SAVE_CONFIG_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "SAVE_CONFIG", self.cmd_SAVE_CONFIG, desc=self.cmd_SAVE_CONFIG_help
+ )
+
def _find_autosave_data(self, data):
regular_data = data
autosave_data = ""
pos = data.find(AUTOSAVE_HEADER)
if pos >= 0:
regular_data = data[:pos]
- autosave_data = data[pos + len(AUTOSAVE_HEADER):].strip()
+ autosave_data = data[pos + len(AUTOSAVE_HEADER) :].strip()
# Check for errors and strip line prefixes
if "\n#*# " in regular_data or autosave_data.find(AUTOSAVE_HEADER) >= 0:
- logging.warning("Can't read autosave from config file"
- " - autosave state corrupted")
+ logging.warning(
+ "Can't read autosave from config file" " - autosave state corrupted"
+ )
return data, ""
out = [""]
- for line in autosave_data.split('\n'):
- if ((not line.startswith("#*#")
- or (len(line) >= 4 and not line.startswith("#*# ")))
- and autosave_data):
- logging.warning("Can't read autosave from config file"
- " - modifications after header")
+ for line in autosave_data.split("\n"):
+ if (
+ not line.startswith("#*#")
+ or (len(line) >= 4 and not line.startswith("#*# "))
+ ) and autosave_data:
+ logging.warning(
+ "Can't read autosave from config file"
+ " - modifications after header"
+ )
return data, ""
out.append(line[4:])
out.append("")
return regular_data, "\n".join(out)
- comment_r = re.compile('[#;].*$')
- value_r = re.compile('[^A-Za-z0-9_].*$')
+
+ comment_r = re.compile("[#;].*$")
+ value_r = re.compile("[^A-Za-z0-9_].*$")
+
def _strip_duplicates(self, data, fileconfig):
# Comment out fields in 'data' that are defined in 'config'
- lines = data.split('\n')
+ lines = data.split("\n")
section = None
is_dup_field = False
for lineno, line in enumerate(lines):
- pruned_line = self.comment_r.sub('', line).rstrip()
+ pruned_line = self.comment_r.sub("", line).rstrip()
if not pruned_line:
continue
if pruned_line[0].isspace():
if is_dup_field:
- lines[lineno] = '#' + lines[lineno]
+ lines[lineno] = "#" + lines[lineno]
continue
is_dup_field = False
- if pruned_line[0] == '[':
+ if pruned_line[0] == "[":
section = pruned_line[1:-1].strip()
continue
- field = self.value_r.sub('', pruned_line)
+ field = self.value_r.sub("", pruned_line)
if fileconfig.has_option(section, field):
is_dup_field = True
- lines[lineno] = '#' + lines[lineno]
+ lines[lineno] = "#" + lines[lineno]
return "\n".join(lines)
+
def load_main_config(self):
- filename = self.printer.get_start_args()['config_file']
+ filename = self.printer.get_start_args()["config_file"]
cfgrdr = ConfigFileReader()
data = cfgrdr.read_config_file(filename)
regular_data, autosave_data = self._find_autosave_data(data)
regular_fileconfig = cfgrdr.build_fileconfig_with_includes(
- regular_data, filename)
- autosave_data = self._strip_duplicates(autosave_data,
- regular_fileconfig)
+ regular_data, filename
+ )
+ autosave_data = self._strip_duplicates(autosave_data, regular_fileconfig)
self.fileconfig = cfgrdr.build_fileconfig(autosave_data, filename)
- cfgrdr.append_fileconfig(regular_fileconfig,
- autosave_data, '*AUTOSAVE*')
+ cfgrdr.append_fileconfig(regular_fileconfig, autosave_data, "*AUTOSAVE*")
return regular_fileconfig, self.fileconfig
+
def get_status(self, eventtime):
- return {'save_config_pending': self.save_config_pending,
- 'save_config_pending_items': self.status_save_pending}
+ return {
+ "save_config_pending": self.save_config_pending,
+ "save_config_pending_items": self.status_save_pending,
+ }
+
def set(self, section, option, value):
if not self.fileconfig.has_section(section):
self.fileconfig.add_section(section)
@@ -328,6 +438,7 @@ class ConfigAutoSave:
self.status_save_pending = pending
self.save_config_pending = True
logging.info("save_config: set [%s] %s = %s", section, option, svalue)
+
def remove_section(self, section):
if self.fileconfig.has_section(section):
self.fileconfig.remove_section(section)
@@ -335,33 +446,39 @@ class ConfigAutoSave:
pending[section] = None
self.status_save_pending = pending
self.save_config_pending = True
- elif (section in self.status_save_pending and
- self.status_save_pending[section] is not None):
+ elif (
+ section in self.status_save_pending
+ and self.status_save_pending[section] is not None
+ ):
pending = dict(self.status_save_pending)
del pending[section]
self.status_save_pending = pending
self.save_config_pending = True
+
def _disallow_include_conflicts(self, regular_fileconfig):
for section in self.fileconfig.sections():
for option in self.fileconfig.options(section):
if regular_fileconfig.has_option(section, option):
- msg = ("SAVE_CONFIG section '%s' option '%s' conflicts "
- "with included value" % (section, option))
+ msg = (
+ "SAVE_CONFIG section '%s' option '%s' conflicts "
+ "with included value" % (section, option)
+ )
raise self.printer.command_error(msg)
+
cmd_SAVE_CONFIG_help = "Overwrite config file and restart"
+
def cmd_SAVE_CONFIG(self, gcmd):
if not self.fileconfig.sections():
return
# Create string containing autosave data
cfgrdr = ConfigFileReader()
autosave_data = cfgrdr.build_config_string(self.fileconfig)
- lines = [('#*# ' + l).strip()
- for l in autosave_data.split('\n')]
+ lines = [("#*# " + l).strip() for l in autosave_data.split("\n")]
lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip())
lines.append("")
- autosave_data = '\n'.join(lines)
+ autosave_data = "\n".join(lines)
# Read in and validate current config file
- cfgname = self.printer.get_start_args()['config_file']
+ cfgname = self.printer.get_start_args()["config_file"]
try:
data = cfgrdr.read_config_file(cfgname)
except error as e:
@@ -374,11 +491,12 @@ class ConfigAutoSave:
new_regular_data, new_autosave_data = self._find_autosave_data(data)
if not new_autosave_data:
raise gcmd.error(
- "Existing config autosave is corrupted."
- " Can't complete SAVE_CONFIG")
+ "Existing config autosave is corrupted." " Can't complete SAVE_CONFIG"
+ )
try:
regular_fileconfig = cfgrdr.build_fileconfig_with_includes(
- new_regular_data, cfgname)
+ new_regular_data, cfgname
+ )
except error as e:
msg = "Unable to parse existing config on SAVE_CONFIG"
logging.exception(msg)
@@ -392,10 +510,9 @@ class ConfigAutoSave:
backup_name = cfgname[:-4] + datestr + ".cfg"
temp_name = cfgname[:-4] + "_autosave.cfg"
# Create new config file with temporary name and swap with main config
- logging.info("SAVE_CONFIG to '%s' (backup in '%s')",
- cfgname, backup_name)
+ logging.info("SAVE_CONFIG to '%s' (backup in '%s')", cfgname, backup_name)
try:
- f = open(temp_name, 'w')
+ f = open(temp_name, "w")
f.write(data)
f.close()
os.rename(cfgname, backup_name)
@@ -405,20 +522,22 @@ class ConfigAutoSave:
logging.exception(msg)
raise gcmd.error(msg)
# Request a restart
- gcode = self.printer.lookup_object('gcode')
- gcode.request_restart('restart')
+ gcode = self.printer.lookup_object("gcode")
+ gcode.request_restart("restart")
######################################################################
# Config validation (check for undefined options)
######################################################################
+
class ConfigValidate:
def __init__(self, printer):
self.printer = printer
self.status_settings = {}
self.access_tracking = {}
self.autosave_options = {}
+
def start_access_tracking(self, autosave_fileconfig):
# Note autosave options for use during undefined options check
self.autosave_options = {}
@@ -427,41 +546,45 @@ class ConfigValidate:
self.autosave_options[(section.lower(), option.lower())] = 1
self.access_tracking = {}
return self.access_tracking
+
def check_unused(self, fileconfig):
# Don't warn on fields set in autosave segment
access_tracking = dict(self.access_tracking)
access_tracking.update(self.autosave_options)
# Note locally used sections
- valid_sections = { s: 1 for s, o in self.printer.lookup_objects() }
- valid_sections.update({ s: 1 for s, o in access_tracking })
+ valid_sections = {s: 1 for s, o in self.printer.lookup_objects()}
+ valid_sections.update({s: 1 for s, o in access_tracking})
# Validate that there are no undefined parameters in the config file
for section_name in fileconfig.sections():
section = section_name.lower()
if section not in valid_sections:
- raise error("Section '%s' is not a valid config section"
- % (section,))
+ raise error("Section '%s' is not a valid config section" % (section,))
for option in fileconfig.options(section_name):
option = option.lower()
if (section, option) not in access_tracking:
- raise error("Option '%s' is not valid in section '%s'"
- % (option, section))
+ raise error(
+ "Option '%s' is not valid in section '%s'" % (option, section)
+ )
# Setup get_status()
self._build_status_settings()
# Clear tracking state
self.access_tracking.clear()
self.autosave_options.clear()
+
def _build_status_settings(self):
self.status_settings = {}
for (section, option), value in self.access_tracking.items():
self.status_settings.setdefault(section, {})[option] = value
+
def get_status(self, eventtime):
- return {'settings': self.status_settings}
+ return {"settings": self.status_settings}
######################################################################
# Main printer config tracking
######################################################################
+
class PrinterConfig:
def __init__(self, printer):
self.printer = printer
@@ -472,35 +595,42 @@ class PrinterConfig:
self.deprecate_warnings = []
self.status_raw_config = {}
self.status_warnings = []
+
def get_printer(self):
return self.printer
+
def read_config(self, filename):
cfgrdr = ConfigFileReader()
data = cfgrdr.read_config_file(filename)
fileconfig = cfgrdr.build_fileconfig(data, filename)
- return ConfigWrapper(self.printer, fileconfig, {}, 'printer')
+ return ConfigWrapper(self.printer, fileconfig, {}, "printer")
+
def read_main_config(self):
fileconfig, autosave_fileconfig = self.autosave.load_main_config()
- access_tracking = self.validate.start_access_tracking(
- autosave_fileconfig)
- config = ConfigWrapper(self.printer, fileconfig,
- access_tracking, 'printer')
+ access_tracking = self.validate.start_access_tracking(autosave_fileconfig)
+ config = ConfigWrapper(self.printer, fileconfig, access_tracking, "printer")
self._build_status_config(config)
return config
+
def log_config(self, config):
cfgrdr = ConfigFileReader()
- lines = ["===== Config file =====",
- cfgrdr.build_config_string(config.fileconfig),
- "======================="]
+ lines = [
+ "===== Config file =====",
+ cfgrdr.build_config_string(config.fileconfig),
+ "=======================",
+ ]
self.printer.set_rollover_info("config", "\n".join(lines))
+
def check_unused_options(self, config):
self.validate.check_unused(config.fileconfig)
+
# Deprecation warnings
def runtime_warning(self, msg):
logging.warning(msg)
- res = {'type': 'runtime_warning', 'message': msg}
+ res = {"type": "runtime_warning", "message": msg}
self.runtime_warnings.append(res)
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
+
def deprecate(self, section, option, value=None, msg=None):
key = (section, option, value)
if key in self.deprecated and self.deprecated[key] == msg:
@@ -509,29 +639,32 @@ class PrinterConfig:
self.deprecate_warnings = []
for (section, option, value), msg in self.deprecated.items():
if value is None:
- res = {'type': 'deprecated_option'}
+ res = {"type": "deprecated_option"}
else:
- res = {'type': 'deprecated_value', 'value': value}
- res['message'] = msg
- res['section'] = section
- res['option'] = option
+ res = {"type": "deprecated_value", "value": value}
+ res["message"] = msg
+ res["section"] = section
+ res["option"] = option
self.deprecate_warnings.append(res)
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
+
# Status reporting
def _build_status_config(self, config):
self.status_raw_config = {}
- for section in config.get_prefix_sections(''):
+ for section in config.get_prefix_sections(""):
self.status_raw_config[section.get_name()] = section_status = {}
- for option in section.get_prefix_options(''):
+ for option in section.get_prefix_options(""):
section_status[option] = section.get(option, note_valid=False)
+
def get_status(self, eventtime):
- status = {'config': self.status_raw_config,
- 'warnings': self.status_warnings}
+ status = {"config": self.status_raw_config, "warnings": self.status_warnings}
status.update(self.autosave.get_status(eventtime))
status.update(self.validate.get_status(eventtime))
return status
+
# Autosave functions
def set(self, section, option, value):
self.autosave.set(section, option, value)
+
def remove_section(self, section):
self.autosave.remove_section(section)
diff --git a/klippy/console.py b/klippy/console.py
index 0a20e09e..3ad6e4eb 100755
--- a/klippy/console.py
+++ b/klippy/console.py
@@ -28,7 +28,8 @@ help_txt = """
freq : The mcu clock frequency
"""
-re_eval = re.compile(r'\{(?P<eval>[^}]*)\}')
+re_eval = re.compile(r"\{(?P<eval>[^}]*)\}")
+
class KeyboardReader:
def __init__(self, reactor, serialport, baud, canbus_iface, canbus_nodeid):
@@ -48,18 +49,24 @@ class KeyboardReader:
reactor.register_callback(self.connect)
self.local_commands = {
"SET": self.command_SET,
- "DUMP": self.command_DUMP, "FILEDUMP": self.command_FILEDUMP,
- "DELAY": self.command_DELAY, "FLOOD": self.command_FLOOD,
- "SUPPRESS": self.command_SUPPRESS, "STATS": self.command_STATS,
- "LIST": self.command_LIST, "HELP": self.command_HELP,
+ "DUMP": self.command_DUMP,
+ "FILEDUMP": self.command_FILEDUMP,
+ "DELAY": self.command_DELAY,
+ "FLOOD": self.command_FLOOD,
+ "SUPPRESS": self.command_SUPPRESS,
+ "STATS": self.command_STATS,
+ "LIST": self.command_LIST,
+ "HELP": self.command_HELP,
}
self.eval_globals = {}
+
def connect(self, eventtime):
self.output(help_txt)
- self.output("="*20 + " attempting to connect " + "="*20)
+ self.output("=" * 20 + " attempting to connect " + "=" * 20)
if self.canbus_iface is not None:
- self.ser.connect_canbus(self.serialport, self.canbus_nodeid,
- self.canbus_iface)
+ self.ser.connect_canbus(
+ self.serialport, self.canbus_nodeid, self.canbus_iface
+ )
elif self.baud:
self.ser.connect_uart(self.serialport, self.baud)
else:
@@ -67,31 +74,44 @@ class KeyboardReader:
msgparser = self.ser.get_msgparser()
message_count = len(msgparser.get_messages())
version, build_versions = msgparser.get_version_info()
- self.output("Loaded %d commands (%s / %s)"
- % (message_count, version, build_versions))
- self.output("MCU config: %s" % (" ".join(
- ["%s=%s" % (k, v) for k, v in msgparser.get_constants().items()])))
+ self.output(
+ "Loaded %d commands (%s / %s)" % (message_count, version, build_versions)
+ )
+ self.output(
+ "MCU config: %s"
+ % (
+ " ".join(
+ ["%s=%s" % (k, v) for k, v in msgparser.get_constants().items()]
+ )
+ )
+ )
self.clocksync.connect(self.ser)
self.ser.handle_default = self.handle_default
- self.ser.register_response(self.handle_output, '#output')
- self.mcu_freq = msgparser.get_constant_float('CLOCK_FREQ')
- self.output("="*20 + " connected " + "="*20)
+ self.ser.register_response(self.handle_output, "#output")
+ self.mcu_freq = msgparser.get_constant_float("CLOCK_FREQ")
+ self.output("=" * 20 + " connected " + "=" * 20)
return self.reactor.NEVER
+
def output(self, msg):
sys.stdout.write("%s\n" % (msg,))
sys.stdout.flush()
+
def handle_default(self, params):
- tdiff = params['#receive_time'] - self.start_time
+ tdiff = params["#receive_time"] - self.start_time
msg = self.ser.get_msgparser().format_params(params)
self.output("%07.3f: %s" % (tdiff, msg))
+
def handle_output(self, params):
- tdiff = params['#receive_time'] - self.start_time
- self.output("%07.3f: %s: %s" % (tdiff, params['#name'], params['#msg']))
+ tdiff = params["#receive_time"] - self.start_time
+ self.output("%07.3f: %s: %s" % (tdiff, params["#name"], params["#msg"]))
+
def handle_suppress(self, params):
pass
+
def update_evals(self, eventtime):
- self.eval_globals['freq'] = self.mcu_freq
- self.eval_globals['clock'] = self.clocksync.get_clock(eventtime)
+ self.eval_globals["freq"] = self.mcu_freq
+ self.eval_globals["clock"] = self.clocksync.get_clock(eventtime)
+
def command_SET(self, parts):
val = parts[2]
try:
@@ -99,6 +119,7 @@ class KeyboardReader:
except ValueError:
pass
self.eval_globals[parts[1]] = val
+
def command_DUMP(self, parts, filename=None):
# Extract command args
try:
@@ -106,7 +127,7 @@ class KeyboardReader:
count = int(parts[2], 0)
order = [2, 0, 1, 0][(addr | count) & 3]
if len(parts) > 3:
- order = {'32': 2, '16': 1, '8': 0}[parts[3]]
+ order = {"32": 2, "16": 1, "8": 0}[parts[3]]
except ValueError as e:
self.output("Error: %s" % (str(e),))
return
@@ -117,23 +138,23 @@ class KeyboardReader:
caddr = addr + (i << order)
cmd = "debug_read order=%d addr=%d" % (order, caddr)
params = self.ser.send_with_response(cmd, "debug_result")
- vals.append(params['val'])
+ vals.append(params["val"])
# Report data
if filename is None and order == 2:
# Common 32bit hex dump
for i in range((len(vals) + 3) // 4):
p = i * 4
- hexvals = " ".join(["%08x" % (v,) for v in vals[p:p+4]])
+ hexvals = " ".join(["%08x" % (v,) for v in vals[p : p + 4]])
self.output("%08x %s" % (addr + p * 4, hexvals))
return
# Convert to byte format
data = bytearray()
for val in vals:
for b in range(bsize):
- data.append((val >> (8 * b)) & 0xff)
+ data.append((val >> (8 * b)) & 0xFF)
data = data[:count]
if filename is not None:
- f = open(filename, 'wb')
+ f = open(filename, "wb")
f.write(data)
f.close()
self.output("Wrote %d bytes to '%s'" % (len(data), filename))
@@ -141,13 +162,15 @@ class KeyboardReader:
for i in range((count + 15) // 16):
p = i * 16
paddr = addr + p
- d = data[p:p+16]
+ d = data[p : p + 16]
hexbytes = " ".join(["%02x" % (v,) for v in d])
- pb = "".join([chr(v) if v >= 0x20 and v < 0x7f else '.' for v in d])
+ pb = "".join([chr(v) if v >= 0x20 and v < 0x7F else "." for v in d])
o = "%08x %-47s |%s|" % (paddr, hexbytes, pb)
self.output("%s %s" % (o[:34], o[34:]))
+
def command_FILEDUMP(self, parts):
self.command_DUMP(parts[1:], filename=parts[1])
+
def command_DELAY(self, parts):
try:
val = int(parts[1])
@@ -155,10 +178,11 @@ class KeyboardReader:
self.output("Error: %s" % (str(e),))
return
try:
- self.ser.send(' '.join(parts[2:]), minclock=val)
+ self.ser.send(" ".join(parts[2:]), minclock=val)
except msgproto.error as e:
self.output("Error: %s" % (str(e),))
return
+
def command_FLOOD(self, parts):
try:
count = int(parts[1])
@@ -166,10 +190,11 @@ class KeyboardReader:
except ValueError as e:
self.output("Error: %s" % (str(e),))
return
- msg = ' '.join(parts[3:])
+ msg = " ".join(parts[3:])
delay_clock = int(delay * self.mcu_freq)
- msg_clock = int(self.clocksync.get_clock(self.reactor.monotonic())
- + self.mcu_freq * .200)
+ msg_clock = int(
+ self.clocksync.get_clock(self.reactor.monotonic()) + self.mcu_freq * 0.200
+ )
try:
for i in range(count):
next_clock = msg_clock + delay_clock
@@ -178,6 +203,7 @@ class KeyboardReader:
except msgproto.error as e:
self.output("Error: %s" % (str(e),))
return
+
def command_SUPPRESS(self, parts):
oid = None
try:
@@ -188,15 +214,19 @@ class KeyboardReader:
self.output("Error: %s" % (str(e),))
return
self.ser.register_response(self.handle_suppress, name, oid)
+
def command_STATS(self, parts):
curtime = self.reactor.monotonic()
- self.output(' '.join([self.ser.stats(curtime),
- self.clocksync.stats(curtime)]))
+ self.output(" ".join([self.ser.stats(curtime), self.clocksync.stats(curtime)]))
+
def command_LIST(self, parts):
self.update_evals(self.reactor.monotonic())
mp = self.ser.get_msgparser()
- cmds = [msgformat for msgtag, msgtype, msgformat in mp.get_messages()
- if msgtype == 'command']
+ cmds = [
+ msgformat
+ for msgtag, msgtype, msgformat in mp.get_messages()
+ if msgtype == "command"
+ ]
out = "Available mcu commands:"
out += "\n ".join([""] + sorted(cmds))
out += "\nAvailable artificial commands:"
@@ -205,8 +235,10 @@ class KeyboardReader:
lvars = sorted(self.eval_globals.items())
out += "\n ".join([""] + ["%s: %s" % (k, v) for k, v in lvars])
self.output(out)
+
def command_HELP(self, parts):
self.output(help_txt)
+
def translate(self, line, eventtime):
evalparts = re_eval.split(line)
if len(evalparts) > 1:
@@ -214,13 +246,13 @@ class KeyboardReader:
try:
for i in range(1, len(evalparts), 2):
e = eval(evalparts[i], dict(self.eval_globals))
- if type(e) == type(0.):
+ if type(e) == type(0.0):
e = int(e)
evalparts[i] = str(e)
except:
self.output("Unable to evaluate: %s" % (line,))
return None
- line = ''.join(evalparts)
+ line = "".join(evalparts)
self.output("Eval: %s" % (line,))
line = line.strip()
if line:
@@ -229,13 +261,14 @@ class KeyboardReader:
self.local_commands[parts[0]](parts)
return None
return line
+
def process_kbd(self, eventtime):
self.data += str(os.read(self.fd, 4096).decode())
- kbdlines = self.data.split('\n')
+ kbdlines = self.data.split("\n")
for line in kbdlines[:-1]:
line = line.strip()
- cpos = line.find('#')
+ cpos = line.find("#")
if cpos >= 0:
line = line[:cpos]
if not line:
@@ -249,24 +282,37 @@ class KeyboardReader:
self.output("Error: %s" % (str(e),))
self.data = kbdlines[-1]
+
def main():
usage = "%prog [options] <serialdevice>"
opts = optparse.OptionParser(usage)
- opts.add_option("-v", action="store_true", dest="verbose",
- help="enable debug messages")
+ opts.add_option(
+ "-v", action="store_true", dest="verbose", help="enable debug messages"
+ )
opts.add_option("-b", "--baud", type="int", dest="baud", help="baud rate")
- opts.add_option("-c", "--canbus_iface", dest="canbus_iface",
- help="Use CAN bus interface; serialdevice is the chip UUID")
- opts.add_option("-i", "--canbus_nodeid", type="int", dest="canbus_nodeid",
- default=64, help="The CAN nodeid to use (default 64)")
+ opts.add_option(
+ "-c",
+ "--canbus_iface",
+ dest="canbus_iface",
+ help="Use CAN bus interface; serialdevice is the chip UUID",
+ )
+ opts.add_option(
+ "-i",
+ "--canbus_nodeid",
+ type="int",
+ dest="canbus_nodeid",
+ default=64,
+ help="The CAN nodeid to use (default 64)",
+ )
options, args = opts.parse_args()
if len(args) != 1:
opts.error("Incorrect number of arguments")
serialport = args[0]
baud = options.baud
- if baud is None and not (serialport.startswith("/dev/rpmsg_")
- or serialport.startswith("/tmp/")):
+ if baud is None and not (
+ serialport.startswith("/dev/rpmsg_") or serialport.startswith("/tmp/")
+ ):
baud = 250000
debuglevel = logging.INFO
@@ -275,12 +321,14 @@ def main():
logging.basicConfig(level=debuglevel)
r = reactor.Reactor()
- kbd = KeyboardReader(r, serialport, baud, options.canbus_iface,
- options.canbus_nodeid)
+ kbd = KeyboardReader(
+ r, serialport, baud, options.canbus_iface, options.canbus_nodeid
+ )
try:
r.run()
except KeyboardInterrupt:
sys.stdout.write("\n")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
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)
diff --git a/klippy/gcode.py b/klippy/gcode.py
index 975da792..427d7992 100644
--- a/klippy/gcode.py
+++ b/klippy/gcode.py
@@ -5,13 +5,17 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, re, logging, collections, shlex
+
class CommandError(Exception):
pass
-Coord = collections.namedtuple('Coord', ('x', 'y', 'z', 'e'))
+
+Coord = collections.namedtuple("Coord", ("x", "y", "z", "e"))
+
class GCodeCommand:
error = CommandError
+
def __init__(self, gcode, command, commandline, params, need_ack):
self._command = command
self._commandline = commandline
@@ -20,12 +24,16 @@ class GCodeCommand:
# Method wrappers
self.respond_info = gcode.respond_info
self.respond_raw = gcode.respond_raw
+
def get_command(self):
return self._command
+
def get_commandline(self):
return self._commandline
+
def get_command_parameters(self):
return self._params
+
def get_raw_command_parameters(self):
command = self._command
origline = self._commandline
@@ -34,12 +42,13 @@ class GCodeCommand:
if origline[:param_start].upper() != command:
# Skip any gcode line-number and ignore any trailing checksum
param_start += origline.upper().find(command)
- end = origline.rfind('*')
- if end >= 0 and origline[end+1:].isdigit():
+ end = origline.rfind("*")
+ if end >= 0 and origline[end + 1 :].isdigit():
param_end = end
- if origline[param_start:param_start+1].isspace():
+ if origline[param_start : param_start + 1].isspace():
param_start += 1
return origline[param_start:param_end]
+
def ack(self, msg=None):
if not self._need_ack:
return False
@@ -49,52 +58,82 @@ class GCodeCommand:
self.respond_raw(ok_msg)
self._need_ack = False
return True
+
# Parameter parsing helpers
- class sentinel: pass
- def get(self, name, default=sentinel, parser=str, minval=None, maxval=None,
- above=None, below=None):
+ class sentinel:
+ pass
+
+ def get(
+ self,
+ name,
+ default=sentinel,
+ parser=str,
+ minval=None,
+ maxval=None,
+ above=None,
+ below=None,
+ ):
value = self._params.get(name)
if value is None:
if default is self.sentinel:
- raise self.error("Error on '%s': missing %s"
- % (self._commandline, name))
+ raise self.error(
+ "Error on '%s': missing %s" % (self._commandline, name)
+ )
return default
try:
value = parser(value)
except:
- raise self.error("Error on '%s': unable to parse %s"
- % (self._commandline, value))
+ raise self.error(
+ "Error on '%s': unable to parse %s" % (self._commandline, value)
+ )
if minval is not None and value < minval:
- raise self.error("Error on '%s': %s must have minimum of %s"
- % (self._commandline, name, minval))
+ raise self.error(
+ "Error on '%s': %s must have minimum of %s"
+ % (self._commandline, name, minval)
+ )
if maxval is not None and value > maxval:
- raise self.error("Error on '%s': %s must have maximum of %s"
- % (self._commandline, name, maxval))
+ raise self.error(
+ "Error on '%s': %s must have maximum of %s"
+ % (self._commandline, name, maxval)
+ )
if above is not None and value <= above:
- raise self.error("Error on '%s': %s must be above %s"
- % (self._commandline, name, above))
+ raise self.error(
+ "Error on '%s': %s must be above %s" % (self._commandline, name, above)
+ )
if below is not None and value >= below:
- raise self.error("Error on '%s': %s must be below %s"
- % (self._commandline, name, below))
+ raise self.error(
+ "Error on '%s': %s must be below %s" % (self._commandline, name, below)
+ )
return value
+
def get_int(self, name, default=sentinel, minval=None, maxval=None):
return self.get(name, default, parser=int, minval=minval, maxval=maxval)
- def get_float(self, name, default=sentinel, minval=None, maxval=None,
- above=None, below=None):
- return self.get(name, default, parser=float, minval=minval,
- maxval=maxval, above=above, below=below)
+
+ def get_float(
+ self, name, default=sentinel, minval=None, maxval=None, above=None, below=None
+ ):
+ return self.get(
+ name,
+ default,
+ parser=float,
+ minval=minval,
+ maxval=maxval,
+ above=above,
+ below=below,
+ )
+
# Parse and dispatch G-Code commands
class GCodeDispatch:
error = CommandError
Coord = Coord
+
def __init__(self, printer):
self.printer = printer
self.is_fileinput = not not printer.get_start_args().get("debuginput")
printer.register_event_handler("klippy:ready", self._handle_ready)
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
- printer.register_event_handler("klippy:disconnect",
- self._handle_disconnect)
+ printer.register_event_handler("klippy:disconnect", self._handle_disconnect)
# Command handling
self.is_printer_ready = False
self.mutex = printer.get_reactor().mutex()
@@ -105,12 +144,21 @@ class GCodeDispatch:
self.gcode_help = {}
self.status_commands = {}
# Register commands needed before config file is loaded
- handlers = ['M110', 'M112', 'M115',
- 'RESTART', 'FIRMWARE_RESTART', 'ECHO', 'STATUS', 'HELP']
+ handlers = [
+ "M110",
+ "M112",
+ "M115",
+ "RESTART",
+ "FIRMWARE_RESTART",
+ "ECHO",
+ "STATUS",
+ "HELP",
+ ]
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)
self.register_command(cmd, func, True, desc)
+
def is_traditional_gcode(self, cmd):
# A "traditional" g-code command is a letter and followed by a number
try:
@@ -119,6 +167,7 @@ class GCodeDispatch:
return cmd[0].isupper() and cmd[1].isdigit()
except:
return False
+
def register_command(self, cmd, func, when_not_ready=False, desc=None):
if func is None:
old_cmd = self.ready_gcode_handlers.get(cmd)
@@ -130,12 +179,18 @@ class GCodeDispatch:
return old_cmd
if cmd in self.ready_gcode_handlers:
raise self.printer.config_error(
- "gcode command %s already registered" % (cmd,))
+ "gcode command %s already registered" % (cmd,)
+ )
if not self.is_traditional_gcode(cmd):
- if (cmd.upper() != cmd or not cmd.replace('_', 'A').isalnum()
- or cmd[0].isdigit() or cmd[1:2].isdigit()):
+ if (
+ cmd.upper() != cmd
+ or not cmd.replace("_", "A").isalnum()
+ or cmd[0].isdigit()
+ or cmd[1:2].isdigit()
+ ):
raise self.printer.config_error(
- "Can't register '%s' as it is an invalid name" % (cmd,))
+ "Can't register '%s' as it is an invalid name" % (cmd,)
+ )
origfunc = func
func = lambda params: origfunc(self._get_extended_params(params))
self.ready_gcode_handlers[cmd] = func
@@ -144,6 +199,7 @@ class GCodeDispatch:
if desc is not None:
self.gcode_help[cmd] = desc
self._build_status_commands()
+
def register_mux_command(self, cmd, key, value, func, desc=None):
prev = self.mux_commands.get(cmd)
if prev is None:
@@ -153,25 +209,32 @@ class GCodeDispatch:
prev_key, prev_values = prev
if prev_key != key:
raise self.printer.config_error(
- "mux command %s %s %s may have only one key (%s)" % (
- cmd, key, value, prev_key))
+ "mux command %s %s %s may have only one key (%s)"
+ % (cmd, key, value, prev_key)
+ )
if value in prev_values:
raise self.printer.config_error(
- "mux command %s %s %s already registered (%s)" % (
- cmd, key, value, prev_values))
+ "mux command %s %s %s already registered (%s)"
+ % (cmd, key, value, prev_values)
+ )
prev_values[value] = func
+
def get_command_help(self):
return dict(self.gcode_help)
+
def get_status(self, eventtime):
- return {'commands': self.status_commands}
+ return {"commands": self.status_commands}
+
def _build_status_commands(self):
commands = {cmd: {} for cmd in self.gcode_handlers}
for cmd in self.gcode_help:
if cmd in commands:
- commands[cmd]['help'] = self.gcode_help[cmd]
+ commands[cmd]["help"] = self.gcode_help[cmd]
self.status_commands = commands
+
def register_output_handler(self, cb):
self.output_callbacks.append(cb)
+
def _handle_shutdown(self):
if not self.is_printer_ready:
return
@@ -179,32 +242,35 @@ class GCodeDispatch:
self.gcode_handlers = self.base_gcode_handlers
self._build_status_commands()
self._respond_state("Shutdown")
+
def _handle_disconnect(self):
self._respond_state("Disconnect")
+
def _handle_ready(self):
self.is_printer_ready = True
self.gcode_handlers = self.ready_gcode_handlers
self._build_status_commands()
self._respond_state("Ready")
+
# Parse input into commands
- args_r = re.compile('([A-Z_]+|[A-Z*])')
+ args_r = re.compile("([A-Z_]+|[A-Z*])")
+
def _process_commands(self, commands, need_ack=True):
for line in commands:
# Ignore comments and leading/trailing spaces
line = origline = line.strip()
- cpos = line.find(';')
+ cpos = line.find(";")
if cpos >= 0:
line = line[:cpos]
# Break line into parts and determine command
parts = self.args_r.split(line.upper())
- if ''.join(parts[:2]) == 'N':
+ if "".join(parts[:2]) == "N":
# Skip line number at start of command
- cmd = ''.join(parts[3:5]).strip()
+ cmd = "".join(parts[3:5]).strip()
else:
- cmd = ''.join(parts[:3]).strip()
+ cmd = "".join(parts[:3]).strip()
# Build gcode "params" dictionary
- params = { parts[i]: parts[i+1].strip()
- for i in range(1, len(parts), 2) }
+ params = {parts[i]: parts[i + 1].strip() for i in range(1, len(parts), 2)}
gcmd = GCodeCommand(self, cmd, origline, params, need_ack)
# Invoke handler for command
handler = self.gcode_handlers.get(cmd, self.cmd_default)
@@ -223,59 +289,68 @@ class GCodeDispatch:
if not need_ack:
raise
gcmd.ack()
+
def run_script_from_command(self, script):
- self._process_commands(script.split('\n'), need_ack=False)
+ self._process_commands(script.split("\n"), need_ack=False)
+
def run_script(self, script):
with self.mutex:
- self._process_commands(script.split('\n'), need_ack=False)
+ self._process_commands(script.split("\n"), need_ack=False)
+
def get_mutex(self):
return self.mutex
+
def create_gcode_command(self, command, commandline, params):
return GCodeCommand(self, command, commandline, params, False)
+
# Response handling
def respond_raw(self, msg):
for cb in self.output_callbacks:
cb(msg)
+
def respond_info(self, msg, log=True):
if log:
logging.info(msg)
- lines = [l.strip() for l in msg.strip().split('\n')]
+ lines = [l.strip() for l in msg.strip().split("\n")]
self.respond_raw("// " + "\n// ".join(lines))
+
def _respond_error(self, msg):
logging.warning(msg)
- lines = msg.strip().split('\n')
+ lines = msg.strip().split("\n")
if len(lines) > 1:
self.respond_info("\n".join(lines), log=False)
- self.respond_raw('!! %s' % (lines[0].strip(),))
+ self.respond_raw("!! %s" % (lines[0].strip(),))
if self.is_fileinput:
- self.printer.request_exit('error_exit')
+ self.printer.request_exit("error_exit")
+
def _respond_state(self, state):
self.respond_info("Klipper state: %s" % (state,), log=False)
+
# Parameter parsing helpers
def _get_extended_params(self, gcmd):
rawparams = gcmd.get_raw_command_parameters()
# Extract args while allowing shell style quoting
s = shlex.shlex(rawparams, posix=True)
s.whitespace_split = True
- s.commenters = '#;'
+ s.commenters = "#;"
try:
- eparams = [earg.split('=', 1) for earg in s]
- eparams = { k.upper(): v for k, v in eparams }
+ eparams = [earg.split("=", 1) for earg in s]
+ eparams = {k.upper(): v for k, v in eparams}
except ValueError as e:
- raise self.error("Malformed command '%s'"
- % (gcmd.get_commandline(),))
+ raise self.error("Malformed command '%s'" % (gcmd.get_commandline(),))
# Update gcmd with new parameters
gcmd._params.clear()
gcmd._params.update(eparams)
return gcmd
+
# G-Code special command handlers
def cmd_default(self, gcmd):
cmd = gcmd.get_command()
- if cmd == 'M105':
+ if cmd == "M105":
# Don't warn about temperature requests when not ready
gcmd.ack("T:0")
return
- if cmd == 'M21':
+ if cmd == "M21":
# Don't warn about sd card init when not ready
return
if not self.is_printer_ready:
@@ -286,7 +361,7 @@ class GCodeDispatch:
if cmdline:
logging.debug(cmdline)
return
- if ' ' in cmd:
+ if " " in cmd:
# Handle M117/M118 gcode with numeric and special characters
realcmd = cmd.split()[0]
if realcmd in ["M117", "M118", "M23"]:
@@ -295,14 +370,16 @@ class GCodeDispatch:
gcmd._command = realcmd
handler(gcmd)
return
- elif cmd in ['M140', 'M104'] and not gcmd.get_float('S', 0.):
+ elif cmd in ["M140", "M104"] and not gcmd.get_float("S", 0.0):
# Don't warn about requests to turn off heaters when not present
return
- elif cmd == 'M107' or (cmd == 'M106' and (
- not gcmd.get_float('S', 1.) or self.is_fileinput)):
+ elif cmd == "M107" or (
+ cmd == "M106" and (not gcmd.get_float("S", 1.0) or self.is_fileinput)
+ ):
# Don't warn about requests to turn off fan when fan not present
return
gcmd.respond_info('Unknown command:"%s"' % (cmd,))
+
def _cmd_mux(self, command, gcmd):
key, values = self.mux_commands[command]
if None in values:
@@ -310,43 +387,53 @@ class GCodeDispatch:
else:
key_param = gcmd.get(key)
if key_param not in values:
- raise gcmd.error("The value '%s' is not valid for %s"
- % (key_param, key))
+ raise gcmd.error("The value '%s' is not valid for %s" % (key_param, key))
values[key_param](gcmd)
+
# Low-level G-Code commands that are needed before the config file is loaded
def cmd_M110(self, gcmd):
# Set Current Line Number
pass
+
def cmd_M112(self, gcmd):
# Emergency Stop
self.printer.invoke_shutdown("Shutdown due to M112 command")
+
def cmd_M115(self, gcmd):
# Get Firmware Version and Capabilities
- software_version = self.printer.get_start_args().get('software_version')
+ software_version = self.printer.get_start_args().get("software_version")
kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version}
msg = " ".join(["%s:%s" % (k, v) for k, v in kw.items()])
did_ack = gcmd.ack(msg)
if not did_ack:
gcmd.respond_info(msg)
+
def request_restart(self, result):
if self.is_printer_ready:
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
print_time = toolhead.get_last_move_time()
- if result == 'exit':
+ if result == "exit":
logging.info("Exiting (print time %.3fs)" % (print_time,))
self.printer.send_event("gcode:request_restart", print_time)
toolhead.dwell(0.500)
toolhead.wait_moves()
self.printer.request_exit(result)
+
cmd_RESTART_help = "Reload config file and restart host software"
+
def cmd_RESTART(self, gcmd):
- self.request_restart('restart')
+ self.request_restart("restart")
+
cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config"
+
def cmd_FIRMWARE_RESTART(self, gcmd):
- self.request_restart('firmware_restart')
+ self.request_restart("firmware_restart")
+
def cmd_ECHO(self, gcmd):
gcmd.respond_info(gcmd.get_commandline(), log=False)
+
cmd_STATUS_help = "Report the printer status"
+
def cmd_STATUS(self, gcmd):
if self.is_printer_ready:
self._respond_state("Ready")
@@ -354,7 +441,9 @@ class GCodeDispatch:
msg = self.printer.get_state_message()[0]
msg = msg.rstrip() + "\nKlipper state: Not ready"
raise gcmd.error(msg)
+
cmd_HELP_help = "Report the list of available extended G-Code commands"
+
def cmd_HELP(self, gcmd):
cmdhelp = []
if not self.is_printer_ready:
@@ -365,13 +454,14 @@ class GCodeDispatch:
cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd]))
gcmd.respond_info("\n".join(cmdhelp), log=False)
+
# Support reading gcode from a pseudo-tty interface
class GCodeIO:
def __init__(self, printer):
self.printer = printer
printer.register_event_handler("klippy:ready", self._handle_ready)
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
- self.gcode = printer.lookup_object('gcode')
+ self.gcode = printer.lookup_object("gcode")
self.gcode_mutex = self.gcode.get_mutex()
self.fd = printer.get_start_args().get("gcode_fd")
self.reactor = printer.get_reactor()
@@ -382,31 +472,34 @@ class GCodeIO:
self.fd_handle = None
if not self.is_fileinput:
self.gcode.register_output_handler(self._respond_raw)
- self.fd_handle = self.reactor.register_fd(self.fd,
- self._process_data)
+ self.fd_handle = self.reactor.register_fd(self.fd, self._process_data)
self.partial_input = ""
self.pending_commands = []
self.bytes_read = 0
self.input_log = collections.deque([], 50)
+
def _handle_ready(self):
self.is_printer_ready = True
if self.is_fileinput and self.fd_handle is None:
- self.fd_handle = self.reactor.register_fd(self.fd,
- self._process_data)
+ self.fd_handle = self.reactor.register_fd(self.fd, self._process_data)
+
def _dump_debug(self):
out = []
out.append("Dumping gcode input %d blocks" % (len(self.input_log),))
for eventtime, data in self.input_log:
out.append("Read %f: %s" % (eventtime, repr(data)))
logging.info("\n".join(out))
+
def _handle_shutdown(self):
if not self.is_printer_ready:
return
self.is_printer_ready = False
self._dump_debug()
if self.is_fileinput:
- self.printer.request_exit('error_exit')
- m112_r = re.compile(r'^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)')
+ self.printer.request_exit("error_exit")
+
+ m112_r = re.compile(r"^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)")
+
def _process_data(self, eventtime):
# Read input, separate by newline, and add to pending_commands
try:
@@ -416,7 +509,7 @@ class GCodeIO:
return
self.input_log.append((eventtime, data))
self.bytes_read += len(data)
- lines = data.split('\n')
+ lines = data.split("\n")
lines[0] = self.partial_input + lines[0]
self.partial_input = lines.pop()
pending_commands = self.pending_commands
@@ -427,7 +520,7 @@ class GCodeIO:
if not self.is_processing_data:
self.reactor.unregister_fd(self.fd_handle)
self.fd_handle = None
- self.gcode.request_restart('exit')
+ self.gcode.request_restart("exit")
pending_commands.append("")
# Handle case where multiple commands pending
if self.is_processing_data or len(pending_commands) > 1:
@@ -451,18 +544,20 @@ class GCodeIO:
pending_commands = self.pending_commands
self.is_processing_data = False
if self.fd_handle is None:
- self.fd_handle = self.reactor.register_fd(self.fd,
- self._process_data)
+ self.fd_handle = self.reactor.register_fd(self.fd, self._process_data)
+
def _respond_raw(self, msg):
if self.pipe_is_active:
try:
- os.write(self.fd, (msg+"\n").encode())
+ os.write(self.fd, (msg + "\n").encode())
except os.error:
logging.exception("Write g-code response")
self.pipe_is_active = False
+
def stats(self, eventtime):
return False, "gcodein=%d" % (self.bytes_read,)
+
def add_early_printer_objects(printer):
- printer.add_object('gcode', GCodeDispatch(printer))
- printer.add_object('gcode_io', GCodeIO(printer))
+ printer.add_object("gcode", GCodeDispatch(printer))
+ printer.add_object("gcode_io", GCodeIO(printer))
diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py
index f9030275..4d21c6e1 100644
--- a/klippy/kinematics/cartesian.py
+++ b/klippy/kinematics/cartesian.py
@@ -7,59 +7,70 @@ import logging
import stepper
from . import idex_modes
+
class CartKinematics:
def __init__(self, toolhead, config):
self.printer = config.get_printer()
# Setup axis rails
self.dual_carriage_axis = None
self.dual_carriage_rails = []
- self.rails = [stepper.LookupMultiRail(config.getsection('stepper_' + n))
- for n in 'xyz']
- for rail, axis in zip(self.rails, 'xyz'):
- rail.setup_itersolve('cartesian_stepper_alloc', axis.encode())
+ self.rails = [
+ stepper.LookupMultiRail(config.getsection("stepper_" + n)) for n in "xyz"
+ ]
+ for rail, axis in zip(self.rails, "xyz"):
+ rail.setup_itersolve("cartesian_stepper_alloc", axis.encode())
ranges = [r.get_range() for r in self.rails]
- self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
- self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
+ self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0)
+ self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0)
self.dc_module = None
- if config.has_section('dual_carriage'):
- dc_config = config.getsection('dual_carriage')
- dc_axis = dc_config.getchoice('axis', ['x', 'y'])
- self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis]
+ if config.has_section("dual_carriage"):
+ dc_config = config.getsection("dual_carriage")
+ dc_axis = dc_config.getchoice("axis", ["x", "y"])
+ self.dual_carriage_axis = {"x": 0, "y": 1}[dc_axis]
# setup second dual carriage rail
self.rails.append(stepper.LookupMultiRail(dc_config))
- self.rails[3].setup_itersolve('cartesian_stepper_alloc',
- dc_axis.encode())
+ self.rails[3].setup_itersolve("cartesian_stepper_alloc", dc_axis.encode())
self.dc_module = idex_modes.DualCarriages(
- self.printer, [self.rails[self.dual_carriage_axis]],
- [self.rails[3]], axes=[self.dual_carriage_axis],
- safe_dist=dc_config.getfloat(
- 'safe_distance', None, minval=0.))
+ self.printer,
+ [self.rails[self.dual_carriage_axis]],
+ [self.rails[3]],
+ axes=[self.dual_carriage_axis],
+ safe_dist=dc_config.getfloat("safe_distance", None, minval=0.0),
+ )
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
- self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
- above=0., maxval=max_velocity)
- self.max_z_accel = config.getfloat('max_z_accel', max_accel,
- above=0., maxval=max_accel)
+ self.max_z_velocity = config.getfloat(
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
+ self.max_z_accel = config.getfloat(
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
self.limits = [(1.0, -1.0)] * 3
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def calc_position(self, stepper_positions):
rails = self.rails
if self.dc_module:
- primary_rail = self.dc_module.get_primary_rail(
- self.dual_carriage_axis)
- rails = (rails[:self.dual_carriage_axis] +
- [primary_rail] + rails[self.dual_carriage_axis+1:])
+ primary_rail = self.dc_module.get_primary_rail(self.dual_carriage_axis)
+ rails = (
+ rails[: self.dual_carriage_axis]
+ + [primary_rail]
+ + rails[self.dual_carriage_axis + 1 :]
+ )
return [stepper_positions[rail.get_name()] for rail in rails]
+
def update_limits(self, i, range):
l, h = self.limits[i]
# Only update limits if this axis was already homed,
# otherwise leave in un-homed state.
if l <= h:
self.limits[i] = range
+
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):
rail.set_position(newpos)
@@ -70,10 +81,12 @@ class CartKinematics:
else:
rail = self.rails[axis]
self.limits[axis] = rail.get_range()
+
def clear_homing_state(self, clear_axes):
for axis, axis_name in enumerate("xyz"):
if axis_name in clear_axes:
self.limits[axis] = (1.0, -1.0)
+
def home_axis(self, homing_state, axis, rail):
# Determine movement
position_min, position_max = rail.get_range()
@@ -87,6 +100,7 @@ class CartKinematics:
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
+
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
@@ -94,20 +108,26 @@ class CartKinematics:
self.dc_module.home(homing_state, self.dual_carriage_axis)
else:
self.home_axis(homing_state, axis, self.rails[axis])
+
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
- if (move.axes_d[i]
- and (end_pos[i] < self.limits[i][0]
- or end_pos[i] > self.limits[i][1])):
+ if move.axes_d[i] and (
+ end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1]
+ ):
if self.limits[i][0] > self.limits[i][1]:
raise move.move_error("Must home axis first")
raise move.move_error()
+
def check_move(self, move):
limits = self.limits
xpos, ypos = move.end_pos[:2]
- if (xpos < limits[0][0] or xpos > limits[0][1]
- or ypos < limits[1][0] or ypos > limits[1][1]):
+ if (
+ xpos < limits[0][0]
+ or xpos > limits[0][1]
+ or ypos < limits[1][0]
+ or ypos > limits[1][1]
+ ):
self._check_endstops(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
@@ -115,15 +135,16 @@ class CartKinematics:
# Move with Z - update velocity and accel for slower Z axis
self._check_endstops(move)
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(
- self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+
def get_status(self, eventtime):
axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h]
return {
- 'homed_axes': "".join(axes),
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
+ "homed_axes": "".join(axes),
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return CartKinematics(toolhead, config)
diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py
index d68f8854..a571b2f6 100644
--- a/klippy/kinematics/corexy.py
+++ b/klippy/kinematics/corexy.py
@@ -6,45 +6,54 @@
import logging, math
import stepper
+
class CoreXYKinematics:
def __init__(self, toolhead, config):
# Setup axis rails
- self.rails = [stepper.LookupMultiRail(config.getsection('stepper_' + n))
- for n in 'xyz']
+ self.rails = [
+ stepper.LookupMultiRail(config.getsection("stepper_" + n)) for n in "xyz"
+ ]
for s in self.rails[1].get_steppers():
self.rails[0].get_endstops()[0][0].add_stepper(s)
for s in self.rails[0].get_steppers():
self.rails[1].get_endstops()[0][0].add_stepper(s)
- self.rails[0].setup_itersolve('corexy_stepper_alloc', b'+')
- self.rails[1].setup_itersolve('corexy_stepper_alloc', b'-')
- self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z')
+ self.rails[0].setup_itersolve("corexy_stepper_alloc", b"+")
+ self.rails[1].setup_itersolve("corexy_stepper_alloc", b"-")
+ self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"z")
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat(
- 'max_z_velocity', max_velocity, above=0., maxval=max_velocity)
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
self.max_z_accel = config.getfloat(
- 'max_z_accel', max_accel, above=0., maxval=max_accel)
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
self.limits = [(1.0, -1.0)] * 3
ranges = [r.get_range() for r in self.rails]
- self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
- self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
+ self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0)
+ self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0)
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [0.5 * (pos[0] + pos[1]), 0.5 * (pos[0] - pos[1]), pos[2]]
+
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):
rail.set_position(newpos)
if "xyz"[i] in homing_axes:
self.limits[i] = rail.get_range()
+
def clear_homing_state(self, clear_axes):
for axis, axis_name in enumerate("xyz"):
if axis_name in clear_axes:
self.limits[axis] = (1.0, -1.0)
+
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
@@ -61,20 +70,26 @@ class CoreXYKinematics:
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
+
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
- if (move.axes_d[i]
- and (end_pos[i] < self.limits[i][0]
- or end_pos[i] > self.limits[i][1])):
+ if move.axes_d[i] and (
+ end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1]
+ ):
if self.limits[i][0] > self.limits[i][1]:
raise move.move_error("Must home axis first")
raise move.move_error()
+
def check_move(self, move):
limits = self.limits
xpos, ypos = move.end_pos[:2]
- if (xpos < limits[0][0] or xpos > limits[0][1]
- or ypos < limits[1][0] or ypos > limits[1][1]):
+ if (
+ xpos < limits[0][0]
+ or xpos > limits[0][1]
+ or ypos < limits[1][0]
+ or ypos > limits[1][1]
+ ):
self._check_endstops(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
@@ -82,15 +97,16 @@ class CoreXYKinematics:
# Move with Z - update velocity and accel for slower Z axis
self._check_endstops(move)
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(
- self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+
def get_status(self, eventtime):
axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h]
return {
- 'homed_axes': "".join(axes),
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
+ "homed_axes": "".join(axes),
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return CoreXYKinematics(toolhead, config)
diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py
index 8d3e027c..b75e00c0 100644
--- a/klippy/kinematics/corexz.py
+++ b/klippy/kinematics/corexz.py
@@ -6,45 +6,54 @@
import logging, math
import stepper
+
class CoreXZKinematics:
def __init__(self, toolhead, config):
# Setup axis rails
- self.rails = [stepper.LookupMultiRail(config.getsection('stepper_' + n))
- for n in 'xyz']
+ self.rails = [
+ stepper.LookupMultiRail(config.getsection("stepper_" + n)) for n in "xyz"
+ ]
for s in self.rails[0].get_steppers():
self.rails[2].get_endstops()[0][0].add_stepper(s)
for s in self.rails[2].get_steppers():
self.rails[0].get_endstops()[0][0].add_stepper(s)
- self.rails[0].setup_itersolve('corexz_stepper_alloc', b'+')
- self.rails[1].setup_itersolve('cartesian_stepper_alloc', b'y')
- self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-')
+ self.rails[0].setup_itersolve("corexz_stepper_alloc", b"+")
+ self.rails[1].setup_itersolve("cartesian_stepper_alloc", b"y")
+ self.rails[2].setup_itersolve("corexz_stepper_alloc", b"-")
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat(
- 'max_z_velocity', max_velocity, above=0., maxval=max_velocity)
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
self.max_z_accel = config.getfloat(
- 'max_z_accel', max_accel, above=0., maxval=max_accel)
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
self.limits = [(1.0, -1.0)] * 3
ranges = [r.get_range() for r in self.rails]
- self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
- self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
+ self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0)
+ self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0)
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [0.5 * (pos[0] + pos[2]), pos[1], 0.5 * (pos[0] - pos[2])]
+
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):
rail.set_position(newpos)
if "xyz"[i] in homing_axes:
self.limits[i] = rail.get_range()
+
def clear_homing_state(self, clear_axes):
for axis, axis_name in enumerate("xyz"):
if axis_name in clear_axes:
self.limits[axis] = (1.0, -1.0)
+
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
@@ -61,20 +70,26 @@ class CoreXZKinematics:
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
+
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
- if (move.axes_d[i]
- and (end_pos[i] < self.limits[i][0]
- or end_pos[i] > self.limits[i][1])):
+ if move.axes_d[i] and (
+ end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1]
+ ):
if self.limits[i][0] > self.limits[i][1]:
raise move.move_error("Must home axis first")
raise move.move_error()
+
def check_move(self, move):
limits = self.limits
xpos, ypos = move.end_pos[:2]
- if (xpos < limits[0][0] or xpos > limits[0][1]
- or ypos < limits[1][0] or ypos > limits[1][1]):
+ if (
+ xpos < limits[0][0]
+ or xpos > limits[0][1]
+ or ypos < limits[1][0]
+ or ypos > limits[1][1]
+ ):
self._check_endstops(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
@@ -82,15 +97,16 @@ class CoreXZKinematics:
# Move with Z - update velocity and accel for slower Z axis
self._check_endstops(move)
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(
- self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+
def get_status(self, eventtime):
axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h]
return {
- 'homed_axes': "".join(axes),
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
+ "homed_axes": "".join(axes),
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return CoreXZKinematics(toolhead, config)
diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py
index aa244a8f..508a3331 100644
--- a/klippy/kinematics/delta.py
+++ b/klippy/kinematics/delta.py
@@ -7,116 +7,148 @@ import math, logging
import stepper, mathutil
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
-SLOW_RATIO = 3.
+SLOW_RATIO = 3.0
+
class DeltaKinematics:
def __init__(self, toolhead, config):
# Setup tower rails
- stepper_configs = [config.getsection('stepper_' + a) for a in 'abc']
- rail_a = stepper.LookupMultiRail(
- stepper_configs[0], need_position_minmax = False)
+ stepper_configs = [config.getsection("stepper_" + a) for a in "abc"]
+ rail_a = stepper.LookupMultiRail(stepper_configs[0], need_position_minmax=False)
a_endstop = rail_a.get_homing_info().position_endstop
rail_b = stepper.LookupMultiRail(
- stepper_configs[1], need_position_minmax = False,
- default_position_endstop=a_endstop)
+ stepper_configs[1],
+ need_position_minmax=False,
+ default_position_endstop=a_endstop,
+ )
rail_c = stepper.LookupMultiRail(
- stepper_configs[2], need_position_minmax = False,
- default_position_endstop=a_endstop)
+ stepper_configs[2],
+ need_position_minmax=False,
+ default_position_endstop=a_endstop,
+ )
self.rails = [rail_a, rail_b, rail_c]
# Setup max velocity
self.max_velocity, self.max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat(
- 'max_z_velocity', self.max_velocity,
- above=0., maxval=self.max_velocity)
- self.max_z_accel = config.getfloat('max_z_accel', self.max_accel,
- above=0., maxval=self.max_accel)
+ "max_z_velocity", self.max_velocity, above=0.0, maxval=self.max_velocity
+ )
+ self.max_z_accel = config.getfloat(
+ "max_z_accel", self.max_accel, above=0.0, maxval=self.max_accel
+ )
# Read radius and arm lengths
- self.radius = radius = config.getfloat('delta_radius', above=0.)
- print_radius = config.getfloat('print_radius', radius, above=0.)
- arm_length_a = stepper_configs[0].getfloat('arm_length', above=radius)
+ self.radius = radius = config.getfloat("delta_radius", above=0.0)
+ print_radius = config.getfloat("print_radius", radius, above=0.0)
+ arm_length_a = stepper_configs[0].getfloat("arm_length", above=radius)
self.arm_lengths = arm_lengths = [
- sconfig.getfloat('arm_length', arm_length_a, above=radius)
- for sconfig in stepper_configs]
+ sconfig.getfloat("arm_length", arm_length_a, above=radius)
+ for sconfig in stepper_configs
+ ]
self.arm2 = [arm**2 for arm in arm_lengths]
- self.abs_endstops = [(rail.get_homing_info().position_endstop
- + math.sqrt(arm2 - radius**2))
- for rail, arm2 in zip(self.rails, self.arm2)]
+ self.abs_endstops = [
+ (rail.get_homing_info().position_endstop + math.sqrt(arm2 - radius**2))
+ for rail, arm2 in zip(self.rails, self.arm2)
+ ]
# Determine tower locations in cartesian space
- self.angles = [sconfig.getfloat('angle', angle)
- for sconfig, angle in zip(stepper_configs,
- [210., 330., 90.])]
- self.towers = [(math.cos(math.radians(angle)) * radius,
- math.sin(math.radians(angle)) * radius)
- for angle in self.angles]
+ self.angles = [
+ sconfig.getfloat("angle", angle)
+ for sconfig, angle in zip(stepper_configs, [210.0, 330.0, 90.0])
+ ]
+ self.towers = [
+ (
+ math.cos(math.radians(angle)) * radius,
+ math.sin(math.radians(angle)) * radius,
+ )
+ for angle in self.angles
+ ]
for r, a, t in zip(self.rails, self.arm2, self.towers):
- r.setup_itersolve('delta_stepper_alloc', a, t[0], t[1])
+ r.setup_itersolve("delta_stepper_alloc", a, t[0], t[1])
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
self.need_home = True
- self.limit_xy2 = -1.
- self.home_position = tuple(
- self._actuator_to_cartesian(self.abs_endstops))
- self.max_z = min([rail.get_homing_info().position_endstop
- for rail in self.rails])
- self.min_z = config.getfloat('minimum_z_position', 0, maxval=self.max_z)
- self.limit_z = min([ep - arm
- for ep, arm in zip(self.abs_endstops, arm_lengths)])
+ self.limit_xy2 = -1.0
+ self.home_position = tuple(self._actuator_to_cartesian(self.abs_endstops))
+ self.max_z = min(
+ [rail.get_homing_info().position_endstop for rail in self.rails]
+ )
+ self.min_z = config.getfloat("minimum_z_position", 0, maxval=self.max_z)
+ self.limit_z = min(
+ [ep - arm for ep, arm in zip(self.abs_endstops, arm_lengths)]
+ )
self.min_arm_length = min_arm_length = min(arm_lengths)
self.min_arm2 = min_arm_length**2
logging.info(
"Delta max build height %.2fmm (radius tapered above %.2fmm)"
- % (self.max_z, self.limit_z))
+ % (self.max_z, self.limit_z)
+ )
# Find the point where an XY move could result in excessive
# tower movement
- half_min_step_dist = min([r.get_steppers()[0].get_step_dist()
- for r in self.rails]) * .5
+ half_min_step_dist = (
+ min([r.get_steppers()[0].get_step_dist() for r in self.rails]) * 0.5
+ )
min_arm_length = min(arm_lengths)
+
def ratio_to_xy(ratio):
- return (ratio * math.sqrt(min_arm_length**2 / (ratio**2 + 1.)
- - half_min_step_dist**2)
- + half_min_step_dist - radius)
- self.slow_xy2 = ratio_to_xy(SLOW_RATIO)**2
- self.very_slow_xy2 = ratio_to_xy(2. * SLOW_RATIO)**2
- self.max_xy2 = min(print_radius, min_arm_length - radius,
- ratio_to_xy(4. * SLOW_RATIO))**2
+ return (
+ ratio
+ * math.sqrt(
+ min_arm_length**2 / (ratio**2 + 1.0) - half_min_step_dist**2
+ )
+ + half_min_step_dist
+ - radius
+ )
+
+ self.slow_xy2 = ratio_to_xy(SLOW_RATIO) ** 2
+ self.very_slow_xy2 = ratio_to_xy(2.0 * SLOW_RATIO) ** 2
+ self.max_xy2 = (
+ min(print_radius, min_arm_length - radius, ratio_to_xy(4.0 * SLOW_RATIO))
+ ** 2
+ )
max_xy = math.sqrt(self.max_xy2)
- logging.info("Delta max build radius %.2fmm (moves slowed past %.2fmm"
- " and %.2fmm)"
- % (max_xy, math.sqrt(self.slow_xy2),
- math.sqrt(self.very_slow_xy2)))
- self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.)
- self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.)
- self.set_position([0., 0., 0.], "")
+ logging.info(
+ "Delta max build radius %.2fmm (moves slowed past %.2fmm"
+ " and %.2fmm)"
+ % (max_xy, math.sqrt(self.slow_xy2), math.sqrt(self.very_slow_xy2))
+ )
+ self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.0)
+ self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.0)
+ self.set_position([0.0, 0.0, 0.0], "")
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def _actuator_to_cartesian(self, spos):
sphere_coords = [(t[0], t[1], sp) for t, sp in zip(self.towers, spos)]
return mathutil.trilateration(sphere_coords, self.arm2)
+
def calc_position(self, stepper_positions):
spos = [stepper_positions[rail.get_name()] for rail in self.rails]
return self._actuator_to_cartesian(spos)
+
def set_position(self, newpos, homing_axes):
for rail in self.rails:
rail.set_position(newpos)
- self.limit_xy2 = -1.
+ self.limit_xy2 = -1.0
if homing_axes == "xyz":
self.need_home = False
+
def clear_homing_state(self, clear_axes):
# Clearing homing state for each axis individually is not implemented
if clear_axes:
self.limit_xy2 = -1
self.need_home = True
+
def home(self, homing_state):
# All axes are homed simultaneously
homing_state.set_axes([0, 1, 2])
forcepos = list(self.home_position)
- forcepos[2] = -1.5 * math.sqrt(max(self.arm2)-self.max_xy2)
+ forcepos[2] = -1.5 * math.sqrt(max(self.arm2) - self.max_xy2)
homing_state.home_rails(self.rails, forcepos, self.home_position)
+
def check_move(self, move):
end_pos = move.end_pos
- end_xy2 = end_pos[0]**2 + end_pos[1]**2
+ end_xy2 = end_pos[0] ** 2 + end_pos[1] ** 2
if end_xy2 <= self.limit_xy2 and not move.axes_d[2]:
# Normal XY move
return
@@ -127,44 +159,48 @@ class DeltaKinematics:
if end_z > self.limit_z:
above_z_limit = end_z - self.limit_z
allowed_radius = self.radius - math.sqrt(
- self.min_arm2 - (self.min_arm_length - above_z_limit)**2
+ self.min_arm2 - (self.min_arm_length - above_z_limit) ** 2
)
limit_xy2 = min(limit_xy2, allowed_radius**2)
if end_xy2 > limit_xy2 or end_z > self.max_z or end_z < self.min_z:
# Move out of range - verify not a homing move
- if (end_pos[:2] != self.home_position[:2]
- or end_z < self.min_z or end_z > self.home_position[2]):
+ if (
+ end_pos[:2] != self.home_position[:2]
+ or end_z < self.min_z
+ or end_z > self.home_position[2]
+ ):
raise move.move_error()
- limit_xy2 = -1.
+ limit_xy2 = -1.0
if move.axes_d[2]:
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(self.max_z_velocity * z_ratio,
- self.max_z_accel * z_ratio)
- limit_xy2 = -1.
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ limit_xy2 = -1.0
# Limit the speed/accel of this move if is is at the extreme
# end of the build envelope
- extreme_xy2 = max(end_xy2, move.start_pos[0]**2 + move.start_pos[1]**2)
+ extreme_xy2 = max(end_xy2, move.start_pos[0] ** 2 + move.start_pos[1] ** 2)
if extreme_xy2 > self.slow_xy2:
r = 0.5
if extreme_xy2 > self.very_slow_xy2:
r = 0.25
move.limit_speed(self.max_velocity * r, self.max_accel * r)
- limit_xy2 = -1.
+ limit_xy2 = -1.0
self.limit_xy2 = min(limit_xy2, self.slow_xy2)
+
def get_status(self, eventtime):
return {
- 'homed_axes': '' if self.need_home else 'xyz',
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
- 'cone_start_z': self.limit_z,
+ "homed_axes": "" if self.need_home else "xyz",
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
+ "cone_start_z": self.limit_z,
}
+
def get_calibration(self):
- endstops = [rail.get_homing_info().position_endstop
- for rail in self.rails]
- stepdists = [rail.get_steppers()[0].get_step_dist()
- for rail in self.rails]
- return DeltaCalibration(self.radius, self.angles, self.arm_lengths,
- endstops, stepdists)
+ endstops = [rail.get_homing_info().position_endstop for rail in self.rails]
+ stepdists = [rail.get_steppers()[0].get_step_dist() for rail in self.rails]
+ return DeltaCalibration(
+ self.radius, self.angles, self.arm_lengths, endstops, stepdists
+ )
+
# Delta parameter calibration for DELTA_CALIBRATE tool
class DeltaCalibration:
@@ -176,67 +212,94 @@ class DeltaCalibration:
self.stepdists = stepdists
# Calculate the XY cartesian coordinates of the delta towers
radian_angles = [math.radians(a) for a in angles]
- self.towers = [(math.cos(a) * radius, math.sin(a) * radius)
- for a in radian_angles]
+ self.towers = [
+ (math.cos(a) * radius, math.sin(a) * radius) for a in radian_angles
+ ]
# Calculate the absolute Z height of each tower endstop
radius2 = radius**2
- self.abs_endstops = [e + math.sqrt(a**2 - radius2)
- for e, a in zip(endstops, arms)]
+ self.abs_endstops = [
+ e + math.sqrt(a**2 - radius2) for e, a in zip(endstops, arms)
+ ]
+
def coordinate_descent_params(self, is_extended):
# Determine adjustment parameters (for use with coordinate_descent)
- adj_params = ('radius', 'angle_a', 'angle_b',
- 'endstop_a', 'endstop_b', 'endstop_c')
+ adj_params = (
+ "radius",
+ "angle_a",
+ "angle_b",
+ "endstop_a",
+ "endstop_b",
+ "endstop_c",
+ )
if is_extended:
- adj_params += ('arm_a', 'arm_b', 'arm_c')
- params = { 'radius': self.radius }
- for i, axis in enumerate('abc'):
- params['angle_'+axis] = self.angles[i]
- params['arm_'+axis] = self.arms[i]
- params['endstop_'+axis] = self.endstops[i]
- params['stepdist_'+axis] = self.stepdists[i]
+ adj_params += ("arm_a", "arm_b", "arm_c")
+ params = {"radius": self.radius}
+ for i, axis in enumerate("abc"):
+ params["angle_" + axis] = self.angles[i]
+ params["arm_" + axis] = self.arms[i]
+ params["endstop_" + axis] = self.endstops[i]
+ params["stepdist_" + axis] = self.stepdists[i]
return adj_params, params
+
def new_calibration(self, params):
# Create a new calibration object from coordinate_descent params
- radius = params['radius']
- angles = [params['angle_'+a] for a in 'abc']
- arms = [params['arm_'+a] for a in 'abc']
- endstops = [params['endstop_'+a] for a in 'abc']
- stepdists = [params['stepdist_'+a] for a in 'abc']
+ radius = params["radius"]
+ angles = [params["angle_" + a] for a in "abc"]
+ arms = [params["arm_" + a] for a in "abc"]
+ endstops = [params["endstop_" + a] for a in "abc"]
+ stepdists = [params["stepdist_" + a] for a in "abc"]
return DeltaCalibration(radius, angles, arms, endstops, stepdists)
+
def get_position_from_stable(self, stable_position):
# Return cartesian coordinates for the given stable_position
sphere_coords = [
(t[0], t[1], es - sp * sd)
- for sd, t, es, sp in zip(self.stepdists, self.towers,
- self.abs_endstops, stable_position) ]
+ for sd, t, es, sp in zip(
+ self.stepdists, self.towers, self.abs_endstops, stable_position
+ )
+ ]
return mathutil.trilateration(sphere_coords, [a**2 for a in self.arms])
+
def calc_stable_position(self, coord):
# Return a stable_position from a cartesian coordinate
steppos = [
- math.sqrt(a**2 - (t[0]-coord[0])**2 - (t[1]-coord[1])**2) + coord[2]
- for t, a in zip(self.towers, self.arms) ]
- return [(ep - sp) / sd
- for sd, ep, sp in zip(self.stepdists,
- self.abs_endstops, steppos)]
+ math.sqrt(a**2 - (t[0] - coord[0]) ** 2 - (t[1] - coord[1]) ** 2) + coord[2]
+ for t, a in zip(self.towers, self.arms)
+ ]
+ return [
+ (ep - sp) / sd
+ for sd, ep, sp in zip(self.stepdists, self.abs_endstops, steppos)
+ ]
+
def save_state(self, configfile):
# Save the current parameters (for use with SAVE_CONFIG)
- configfile.set('printer', 'delta_radius', "%.6f" % (self.radius,))
- for i, axis in enumerate('abc'):
- configfile.set('stepper_'+axis, 'angle', "%.6f" % (self.angles[i],))
- configfile.set('stepper_'+axis, 'arm_length',
- "%.6f" % (self.arms[i],))
- configfile.set('stepper_'+axis, 'position_endstop',
- "%.6f" % (self.endstops[i],))
+ configfile.set("printer", "delta_radius", "%.6f" % (self.radius,))
+ for i, axis in enumerate("abc"):
+ configfile.set("stepper_" + axis, "angle", "%.6f" % (self.angles[i],))
+ configfile.set("stepper_" + axis, "arm_length", "%.6f" % (self.arms[i],))
+ configfile.set(
+ "stepper_" + axis, "position_endstop", "%.6f" % (self.endstops[i],)
+ )
gcode = configfile.get_printer().lookup_object("gcode")
gcode.respond_info(
"stepper_a: position_endstop: %.6f angle: %.6f arm_length: %.6f\n"
"stepper_b: position_endstop: %.6f angle: %.6f arm_length: %.6f\n"
"stepper_c: position_endstop: %.6f angle: %.6f arm_length: %.6f\n"
"delta_radius: %.6f"
- % (self.endstops[0], self.angles[0], self.arms[0],
- self.endstops[1], self.angles[1], self.arms[1],
- self.endstops[2], self.angles[2], self.arms[2],
- self.radius))
+ % (
+ self.endstops[0],
+ self.angles[0],
+ self.arms[0],
+ self.endstops[1],
+ self.angles[1],
+ self.arms[1],
+ self.endstops[2],
+ self.angles[2],
+ self.arms[2],
+ self.radius,
+ )
+ )
+
def load_kinematics(toolhead, config):
return DeltaKinematics(toolhead, config)
diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py
index 54b013a5..c2aec720 100644
--- a/klippy/kinematics/deltesian.py
+++ b/klippy/kinematics/deltesian.py
@@ -7,50 +7,51 @@ import math, logging
import stepper
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
-SLOW_RATIO = 3.
+SLOW_RATIO = 3.0
# Minimum angle with the horizontal for the arm to not exceed - in degrees
-MIN_ANGLE = 5.
+MIN_ANGLE = 5.0
+
class DeltesianKinematics:
def __init__(self, toolhead, config):
self.rails = [None] * 3
- stepper_configs = [config.getsection('stepper_' + s)
- for s in ['left', 'right', 'y']]
+ stepper_configs = [
+ config.getsection("stepper_" + s) for s in ["left", "right", "y"]
+ ]
self.rails[0] = stepper.LookupRail(
- stepper_configs[0], need_position_minmax = False)
+ stepper_configs[0], need_position_minmax=False
+ )
def_pos_es = self.rails[0].get_homing_info().position_endstop
self.rails[1] = stepper.LookupRail(
- stepper_configs[1], need_position_minmax = False,
- default_position_endstop = def_pos_es)
+ stepper_configs[1],
+ need_position_minmax=False,
+ default_position_endstop=def_pos_es,
+ )
self.rails[2] = stepper.LookupMultiRail(stepper_configs[2])
arm_x = self.arm_x = [None] * 2
- arm_x[0] = stepper_configs[0].getfloat('arm_x_length', above=0.)
- arm_x[1] = stepper_configs[1].getfloat('arm_x_length', arm_x[0],
- above=0.)
+ arm_x[0] = stepper_configs[0].getfloat("arm_x_length", above=0.0)
+ arm_x[1] = stepper_configs[1].getfloat("arm_x_length", arm_x[0], above=0.0)
arm = [None] * 2
- arm[0] = stepper_configs[0].getfloat('arm_length', above=arm_x[0])
- arm[1] = stepper_configs[1].getfloat('arm_length', arm[0],
- above=arm_x[1])
+ arm[0] = stepper_configs[0].getfloat("arm_length", above=arm_x[0])
+ arm[1] = stepper_configs[1].getfloat("arm_length", arm[0], above=arm_x[1])
arm2 = self.arm2 = [a**2 for a in arm]
- self.rails[0].setup_itersolve(
- 'deltesian_stepper_alloc', arm2[0], -arm_x[0])
- self.rails[1].setup_itersolve(
- 'deltesian_stepper_alloc', arm2[1], arm_x[1])
- self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y')
+ self.rails[0].setup_itersolve("deltesian_stepper_alloc", arm2[0], -arm_x[0])
+ self.rails[1].setup_itersolve("deltesian_stepper_alloc", arm2[1], arm_x[1])
+ self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"y")
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
self.limits = [(1.0, -1.0)] * 3
# X axis limits
- min_angle = config.getfloat('min_angle', MIN_ANGLE,
- minval=0., maxval=90.)
+ min_angle = config.getfloat("min_angle", MIN_ANGLE, minval=0.0, maxval=90.0)
cos_angle = math.cos(math.radians(min_angle))
- x_kin_min = math.ceil( -min(arm_x[0], cos_angle * arm[1] - arm_x[1]))
- x_kin_max = math.floor( min(arm_x[1], cos_angle * arm[0] - arm_x[0]))
+ x_kin_min = math.ceil(-min(arm_x[0], cos_angle * arm[1] - arm_x[1]))
+ x_kin_max = math.floor(min(arm_x[1], cos_angle * arm[0] - arm_x[0]))
x_kin_range = min(x_kin_max - x_kin_min, x_kin_max * 2, -x_kin_min * 2)
- print_width = config.getfloat('print_width', None, minval=0.,
- maxval=x_kin_range)
+ print_width = config.getfloat(
+ "print_width", None, minval=0.0, maxval=x_kin_range
+ )
if print_width:
self.limits[0] = (-print_width * 0.5, print_width * 0.5)
else:
@@ -59,37 +60,56 @@ class DeltesianKinematics:
self.limits[1] = self.rails[2].get_range()
# Z axis limits
pmax = [r.get_homing_info().position_endstop for r in self.rails[:2]]
- self._abs_endstop = [p + math.sqrt(a2 - ax**2) for p, a2, ax
- in zip( pmax, arm2, arm_x )]
+ self._abs_endstop = [
+ p + math.sqrt(a2 - ax**2) for p, a2, ax in zip(pmax, arm2, arm_x)
+ ]
self.home_z = self._actuator_to_cartesian(self._abs_endstop)[1]
z_max = min([self._pillars_z_max(x) for x in self.limits[0]])
- z_min = config.getfloat('minimum_z_position', 0, maxval=z_max)
+ z_min = config.getfloat("minimum_z_position", 0, maxval=z_max)
self.limits[2] = (z_min, z_max)
# Limit the speed/accel if is is at the extreme values of the x axis
- slow_ratio = config.getfloat('slow_ratio', SLOW_RATIO, minval=0.)
+ slow_ratio = config.getfloat("slow_ratio", SLOW_RATIO, minval=0.0)
self.slow_x2 = self.very_slow_x2 = None
- if slow_ratio > 0.:
- sr2 = slow_ratio ** 2
- self.slow_x2 = min([math.sqrt((sr2 * a2) / (sr2 + 1))
- - axl for a2, axl in zip(arm2, arm_x)]) ** 2
- self.very_slow_x2 = min([math.sqrt((2 * sr2 * a2) / (2 * sr2 + 1))
- - axl for a2, axl in zip(arm2, arm_x)]) ** 2
- logging.info("Deltesian kinematics: moves slowed past %.2fmm"
- " and %.2fmm"
- % (math.sqrt(self.slow_x2),
- math.sqrt(self.very_slow_x2)))
+ if slow_ratio > 0.0:
+ sr2 = slow_ratio**2
+ self.slow_x2 = (
+ min(
+ [
+ math.sqrt((sr2 * a2) / (sr2 + 1)) - axl
+ for a2, axl in zip(arm2, arm_x)
+ ]
+ )
+ ** 2
+ )
+ self.very_slow_x2 = (
+ min(
+ [
+ math.sqrt((2 * sr2 * a2) / (2 * sr2 + 1)) - axl
+ for a2, axl in zip(arm2, arm_x)
+ ]
+ )
+ ** 2
+ )
+ logging.info(
+ "Deltesian kinematics: moves slowed past %.2fmm"
+ " and %.2fmm" % (math.sqrt(self.slow_x2), math.sqrt(self.very_slow_x2))
+ )
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
- self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
- above=0., maxval=max_velocity)
- self.max_z_accel = config.getfloat('max_z_accel', max_accel,
- above=0., maxval=max_accel)
- self.axes_min = toolhead.Coord(*[l[0] for l in self.limits], e=0.)
- self.axes_max = toolhead.Coord(*[l[1] for l in self.limits], e=0.)
+ self.max_z_velocity = config.getfloat(
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
+ self.max_z_accel = config.getfloat(
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
+ self.axes_min = toolhead.Coord(*[l[0] for l in self.limits], e=0.0)
+ self.axes_max = toolhead.Coord(*[l[1] for l in self.limits], e=0.0)
self.homed_axis = [False] * 3
- self.set_position([0., 0., 0.], "")
+ self.set_position([0.0, 0.0, 0.0], "")
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def _actuator_to_cartesian(self, sp):
arm_x, arm2 = self.arm_x, self.arm2
dx, dz = sum(arm_x), sp[1] - sp[0]
@@ -101,38 +121,48 @@ class DeltesianKinematics:
x = xt * dx / pivots + zt * dz / pivots - arm_x[0]
z = xt * dz / pivots - zt * dx / pivots + sp[0]
return [x, z]
+
def _pillars_z_max(self, x):
arm_x, arm2 = self.arm_x, self.arm2
- dz = (math.sqrt(arm2[0] - (arm_x[0] + x)**2),
- math.sqrt(arm2[1] - (arm_x[1] - x)**2))
+ dz = (
+ math.sqrt(arm2[0] - (arm_x[0] + x) ** 2),
+ math.sqrt(arm2[1] - (arm_x[1] - x) ** 2),
+ )
return min([o - z for o, z in zip(self._abs_endstop, dz)])
+
def calc_position(self, stepper_positions):
sp = [stepper_positions[rail.get_name()] for rail in self.rails]
x, z = self._actuator_to_cartesian(sp[:2])
return [x, sp[2], z]
+
def set_position(self, newpos, homing_axes):
for rail in self.rails:
rail.set_position(newpos)
for axis_name in homing_axes:
axis = "xyz".index(axis_name)
self.homed_axis[axis] = True
+
def clear_homing_state(self, clear_axes):
for axis, axis_name in enumerate("xyz"):
if axis_name in clear_axes:
self.homed_axis[axis] = False
+
def home(self, homing_state):
homing_axes = homing_state.get_axes()
home_xz = 0 in homing_axes or 2 in homing_axes
home_y = 1 in homing_axes
- forceaxes = ([0, 1, 2] if (home_xz and home_y) else
- [0, 2] if home_xz else [1] if home_y else [])
+ forceaxes = (
+ [0, 1, 2]
+ if (home_xz and home_y)
+ else [0, 2] if home_xz else [1] if home_y else []
+ )
homing_state.set_axes(forceaxes)
homepos = [None] * 4
if home_xz:
homing_state.set_axes([0, 1, 2] if home_y else [0, 2])
- homepos[0], homepos[2] = 0., self.home_z
+ homepos[0], homepos[2] = 0.0, self.home_z
forcepos = list(homepos)
- dz2 = [(a2 - ax ** 2) for a2, ax in zip(self.arm2, self.arm_x)]
+ dz2 = [(a2 - ax**2) for a2, ax in zip(self.arm2, self.arm_x)]
forcepos[2] = -1.5 * math.sqrt(max(dz2))
homing_state.home_rails(self.rails[:2], forcepos, homepos)
if home_y:
@@ -145,11 +175,12 @@ class DeltesianKinematics:
else:
forcepos[1] += 1.5 * (position_max - hi.position_endstop)
homing_state.home_rails([self.rails[2]], forcepos, homepos)
+
def check_move(self, move):
limits = list(map(list, self.limits))
spos, epos = move.start_pos, move.end_pos
homing_move = False
- if epos[0] == 0. and epos[2] == self.home_z and not move.axes_d[1]:
+ if epos[0] == 0.0 and epos[2] == self.home_z and not move.axes_d[1]:
# Identify a homing move
homing_move = True
elif epos[2] > limits[2][1]:
@@ -164,22 +195,23 @@ class DeltesianKinematics:
raise move.move_error()
if move.axes_d[2]:
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(
- self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
# Limit the speed/accel if is is at the extreme values of the x axis
if move.axes_d[0] and self.slow_x2 and self.very_slow_x2:
move_x2 = max(spos[0] ** 2, epos[0] ** 2)
if move_x2 > self.very_slow_x2:
- move.limit_speed(self.max_velocity *0.25, self.max_accel *0.25)
+ move.limit_speed(self.max_velocity * 0.25, self.max_accel * 0.25)
elif move_x2 > self.slow_x2:
- move.limit_speed(self.max_velocity *0.50, self.max_accel *0.50)
+ move.limit_speed(self.max_velocity * 0.50, self.max_accel * 0.50)
+
def get_status(self, eventtime):
axes = [a for a, b in zip("xyz", self.homed_axis) if b]
return {
- 'homed_axes': "".join(axes),
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
+ "homed_axes": "".join(axes),
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return DeltesianKinematics(toolhead, config)
diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py
index 58ccdbec..25ac394d 100644
--- a/klippy/kinematics/extruder.py
+++ b/klippy/kinematics/extruder.py
@@ -6,51 +6,75 @@
import math, logging
import stepper, chelper
+
class ExtruderStepper:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
- self.pressure_advance = self.pressure_advance_smooth_time = 0.
- self.config_pa = config.getfloat('pressure_advance', 0., minval=0.)
+ self.pressure_advance = self.pressure_advance_smooth_time = 0.0
+ self.config_pa = config.getfloat("pressure_advance", 0.0, minval=0.0)
self.config_smooth_time = config.getfloat(
- 'pressure_advance_smooth_time', 0.040, above=0., maxval=.200)
+ "pressure_advance_smooth_time", 0.040, above=0.0, maxval=0.200
+ )
# Setup stepper
self.stepper = stepper.PrinterStepper(config)
ffi_main, ffi_lib = chelper.get_ffi()
- self.sk_extruder = ffi_main.gc(ffi_lib.extruder_stepper_alloc(),
- ffi_lib.extruder_stepper_free)
+ self.sk_extruder = ffi_main.gc(
+ ffi_lib.extruder_stepper_alloc(), ffi_lib.extruder_stepper_free
+ )
self.stepper.set_stepper_kinematics(self.sk_extruder)
self.motion_queue = None
# Register commands
- self.printer.register_event_handler("klippy:connect",
- self._handle_connect)
- gcode = self.printer.lookup_object('gcode')
- if self.name == 'extruder':
- gcode.register_mux_command("SET_PRESSURE_ADVANCE", "EXTRUDER", None,
- self.cmd_default_SET_PRESSURE_ADVANCE,
- desc=self.cmd_SET_PRESSURE_ADVANCE_help)
- gcode.register_mux_command("SET_PRESSURE_ADVANCE", "EXTRUDER",
- self.name, self.cmd_SET_PRESSURE_ADVANCE,
- desc=self.cmd_SET_PRESSURE_ADVANCE_help)
- gcode.register_mux_command("SET_EXTRUDER_ROTATION_DISTANCE", "EXTRUDER",
- self.name, self.cmd_SET_E_ROTATION_DISTANCE,
- desc=self.cmd_SET_E_ROTATION_DISTANCE_help)
- gcode.register_mux_command("SYNC_EXTRUDER_MOTION", "EXTRUDER",
- self.name, self.cmd_SYNC_EXTRUDER_MOTION,
- desc=self.cmd_SYNC_EXTRUDER_MOTION_help)
+ self.printer.register_event_handler("klippy:connect", self._handle_connect)
+ gcode = self.printer.lookup_object("gcode")
+ if self.name == "extruder":
+ gcode.register_mux_command(
+ "SET_PRESSURE_ADVANCE",
+ "EXTRUDER",
+ None,
+ self.cmd_default_SET_PRESSURE_ADVANCE,
+ desc=self.cmd_SET_PRESSURE_ADVANCE_help,
+ )
+ gcode.register_mux_command(
+ "SET_PRESSURE_ADVANCE",
+ "EXTRUDER",
+ self.name,
+ self.cmd_SET_PRESSURE_ADVANCE,
+ desc=self.cmd_SET_PRESSURE_ADVANCE_help,
+ )
+ gcode.register_mux_command(
+ "SET_EXTRUDER_ROTATION_DISTANCE",
+ "EXTRUDER",
+ self.name,
+ self.cmd_SET_E_ROTATION_DISTANCE,
+ desc=self.cmd_SET_E_ROTATION_DISTANCE_help,
+ )
+ gcode.register_mux_command(
+ "SYNC_EXTRUDER_MOTION",
+ "EXTRUDER",
+ self.name,
+ self.cmd_SYNC_EXTRUDER_MOTION,
+ desc=self.cmd_SYNC_EXTRUDER_MOTION_help,
+ )
+
def _handle_connect(self):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.register_step_generator(self.stepper.generate_steps)
self._set_pressure_advance(self.config_pa, self.config_smooth_time)
+
def get_status(self, eventtime):
- return {'pressure_advance': self.pressure_advance,
- 'smooth_time': self.pressure_advance_smooth_time,
- 'motion_queue': self.motion_queue}
+ return {
+ "pressure_advance": self.pressure_advance,
+ "smooth_time": self.pressure_advance_smooth_time,
+ "motion_queue": self.motion_queue,
+ }
+
def find_past_position(self, print_time):
mcu_pos = self.stepper.get_past_mcu_position(print_time)
return self.stepper.mcu_to_commanded_position(mcu_pos)
+
def sync_to_extruder(self, extruder_name):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
if not extruder_name:
self.stepper.set_trapq(None)
@@ -58,62 +82,72 @@ class ExtruderStepper:
return
extruder = self.printer.lookup_object(extruder_name, None)
if extruder is None or not isinstance(extruder, PrinterExtruder):
- raise self.printer.command_error("'%s' is not a valid extruder."
- % (extruder_name,))
- self.stepper.set_position([extruder.last_position, 0., 0.])
+ raise self.printer.command_error(
+ "'%s' is not a valid extruder." % (extruder_name,)
+ )
+ self.stepper.set_position([extruder.last_position, 0.0, 0.0])
self.stepper.set_trapq(extruder.get_trapq())
self.motion_queue = extruder_name
+
def _set_pressure_advance(self, pressure_advance, smooth_time):
old_smooth_time = self.pressure_advance_smooth_time
if not self.pressure_advance:
- old_smooth_time = 0.
+ old_smooth_time = 0.0
new_smooth_time = smooth_time
if not pressure_advance:
- new_smooth_time = 0.
+ new_smooth_time = 0.0
toolhead = self.printer.lookup_object("toolhead")
if new_smooth_time != old_smooth_time:
toolhead.note_step_generation_scan_time(
- new_smooth_time * .5, old_delay=old_smooth_time * .5)
+ new_smooth_time * 0.5, old_delay=old_smooth_time * 0.5
+ )
ffi_main, ffi_lib = chelper.get_ffi()
espa = ffi_lib.extruder_set_pressure_advance
toolhead.register_lookahead_callback(
- lambda print_time: espa(self.sk_extruder, print_time,
- pressure_advance, new_smooth_time))
+ lambda print_time: espa(
+ self.sk_extruder, print_time, pressure_advance, new_smooth_time
+ )
+ )
self.pressure_advance = pressure_advance
self.pressure_advance_smooth_time = smooth_time
+
cmd_SET_PRESSURE_ADVANCE_help = "Set pressure advance parameters"
+
def cmd_default_SET_PRESSURE_ADVANCE(self, gcmd):
- extruder = self.printer.lookup_object('toolhead').get_extruder()
+ extruder = self.printer.lookup_object("toolhead").get_extruder()
if extruder.extruder_stepper is None:
raise gcmd.error("Active extruder does not have a stepper")
strapq = extruder.extruder_stepper.stepper.get_trapq()
if strapq is not extruder.get_trapq():
raise gcmd.error("Unable to infer active extruder stepper")
extruder.extruder_stepper.cmd_SET_PRESSURE_ADVANCE(gcmd)
+
def cmd_SET_PRESSURE_ADVANCE(self, gcmd):
- pressure_advance = gcmd.get_float('ADVANCE', self.pressure_advance,
- minval=0.)
- smooth_time = gcmd.get_float('SMOOTH_TIME',
- self.pressure_advance_smooth_time,
- minval=0., maxval=.200)
+ pressure_advance = gcmd.get_float("ADVANCE", self.pressure_advance, minval=0.0)
+ smooth_time = gcmd.get_float(
+ "SMOOTH_TIME", self.pressure_advance_smooth_time, minval=0.0, maxval=0.200
+ )
self._set_pressure_advance(pressure_advance, smooth_time)
- msg = ("pressure_advance: %.6f\n"
- "pressure_advance_smooth_time: %.6f"
- % (pressure_advance, smooth_time))
+ msg = "pressure_advance: %.6f\n" "pressure_advance_smooth_time: %.6f" % (
+ pressure_advance,
+ smooth_time,
+ )
self.printer.set_rollover_info(self.name, "%s: %s" % (self.name, msg))
gcmd.respond_info(msg, log=False)
+
cmd_SET_E_ROTATION_DISTANCE_help = "Set extruder rotation distance"
+
def cmd_SET_E_ROTATION_DISTANCE(self, gcmd):
- rotation_dist = gcmd.get_float('DISTANCE', None)
+ rotation_dist = gcmd.get_float("DISTANCE", None)
if rotation_dist is not None:
if not rotation_dist:
raise gcmd.error("Rotation distance can not be zero")
invert_dir, orig_invert_dir = self.stepper.get_dir_inverted()
next_invert_dir = orig_invert_dir
- if rotation_dist < 0.:
+ if rotation_dist < 0.0:
next_invert_dir = not orig_invert_dir
rotation_dist = -rotation_dist
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
self.stepper.set_rotation_distance(rotation_dist)
self.stepper.set_dir_inverted(next_invert_dir)
@@ -122,48 +156,53 @@ class ExtruderStepper:
invert_dir, orig_invert_dir = self.stepper.get_dir_inverted()
if invert_dir != orig_invert_dir:
rotation_dist = -rotation_dist
- gcmd.respond_info("Extruder '%s' rotation distance set to %0.6f"
- % (self.name, rotation_dist))
+ gcmd.respond_info(
+ "Extruder '%s' rotation distance set to %0.6f" % (self.name, rotation_dist)
+ )
+
cmd_SYNC_EXTRUDER_MOTION_help = "Set extruder stepper motion queue"
+
def cmd_SYNC_EXTRUDER_MOTION(self, gcmd):
- ename = gcmd.get('MOTION_QUEUE')
+ ename = gcmd.get("MOTION_QUEUE")
self.sync_to_extruder(ename)
- gcmd.respond_info("Extruder '%s' now syncing with '%s'"
- % (self.name, ename))
+ gcmd.respond_info("Extruder '%s' now syncing with '%s'" % (self.name, ename))
+
# Tracking for hotend heater, extrusion motion queue, and extruder stepper
class PrinterExtruder:
def __init__(self, config, extruder_num):
self.printer = config.get_printer()
self.name = config.get_name()
- self.last_position = 0.
+ self.last_position = 0.0
# Setup hotend heater
- pheaters = self.printer.load_object(config, 'heaters')
- gcode_id = 'T%d' % (extruder_num,)
+ pheaters = self.printer.load_object(config, "heaters")
+ gcode_id = "T%d" % (extruder_num,)
self.heater = pheaters.setup_heater(config, gcode_id)
# Setup kinematic checks
- self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.)
+ self.nozzle_diameter = config.getfloat("nozzle_diameter", above=0.0)
filament_diameter = config.getfloat(
- 'filament_diameter', minval=self.nozzle_diameter)
- self.filament_area = math.pi * (filament_diameter * .5)**2
- def_max_cross_section = 4. * self.nozzle_diameter**2
+ "filament_diameter", minval=self.nozzle_diameter
+ )
+ self.filament_area = math.pi * (filament_diameter * 0.5) ** 2
+ def_max_cross_section = 4.0 * self.nozzle_diameter**2
def_max_extrude_ratio = def_max_cross_section / self.filament_area
max_cross_section = config.getfloat(
- 'max_extrude_cross_section', def_max_cross_section, above=0.)
+ "max_extrude_cross_section", def_max_cross_section, above=0.0
+ )
self.max_extrude_ratio = max_cross_section / self.filament_area
logging.info("Extruder max_extrude_ratio=%.6f", self.max_extrude_ratio)
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_e_velocity = config.getfloat(
- 'max_extrude_only_velocity', max_velocity * def_max_extrude_ratio
- , above=0.)
+ "max_extrude_only_velocity", max_velocity * def_max_extrude_ratio, above=0.0
+ )
self.max_e_accel = config.getfloat(
- 'max_extrude_only_accel', max_accel * def_max_extrude_ratio
- , above=0.)
- self.max_e_dist = config.getfloat(
- 'max_extrude_only_distance', 50., minval=0.)
+ "max_extrude_only_accel", max_accel * def_max_extrude_ratio, above=0.0
+ )
+ self.max_e_dist = config.getfloat("max_extrude_only_distance", 50.0, minval=0.0)
self.instant_corner_v = config.getfloat(
- 'instantaneous_corner_velocity', 1., minval=0.)
+ "instantaneous_corner_velocity", 1.0, minval=0.0
+ )
# Setup extruder trapq (trapezoidal motion queue)
ffi_main, ffi_lib = chelper.get_ffi()
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
@@ -171,111 +210,151 @@ class PrinterExtruder:
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
# Setup extruder stepper
self.extruder_stepper = None
- if (config.get('step_pin', None) is not None
- or config.get('dir_pin', None) is not None
- or config.get('rotation_distance', None) is not None):
+ if (
+ config.get("step_pin", None) is not None
+ or config.get("dir_pin", None) is not None
+ or config.get("rotation_distance", None) is not None
+ ):
self.extruder_stepper = ExtruderStepper(config)
self.extruder_stepper.stepper.set_trapq(self.trapq)
# Register commands
- gcode = self.printer.lookup_object('gcode')
- if self.name == 'extruder':
- toolhead.set_extruder(self, 0.)
+ gcode = self.printer.lookup_object("gcode")
+ if self.name == "extruder":
+ toolhead.set_extruder(self, 0.0)
gcode.register_command("M104", self.cmd_M104)
gcode.register_command("M109", self.cmd_M109)
- gcode.register_mux_command("ACTIVATE_EXTRUDER", "EXTRUDER",
- self.name, self.cmd_ACTIVATE_EXTRUDER,
- desc=self.cmd_ACTIVATE_EXTRUDER_help)
+ gcode.register_mux_command(
+ "ACTIVATE_EXTRUDER",
+ "EXTRUDER",
+ self.name,
+ self.cmd_ACTIVATE_EXTRUDER,
+ desc=self.cmd_ACTIVATE_EXTRUDER_help,
+ )
+
def get_status(self, eventtime):
sts = self.heater.get_status(eventtime)
- sts['can_extrude'] = self.heater.can_extrude
+ sts["can_extrude"] = self.heater.can_extrude
if self.extruder_stepper is not None:
sts.update(self.extruder_stepper.get_status(eventtime))
return sts
+
def get_name(self):
return self.name
+
def get_heater(self):
return self.heater
+
def get_trapq(self):
return self.trapq
+
def get_axis_gcode_id(self):
- return 'E'
+ return "E"
+
def stats(self, eventtime):
return self.heater.stats(eventtime)
+
def check_move(self, move, ea_index):
if not self.heater.can_extrude:
raise self.printer.command_error(
"Extrude below minimum temp\n"
- "See the 'min_extrude_temp' config option for details")
+ "See the 'min_extrude_temp' config option for details"
+ )
axis_r = move.axes_r[ea_index]
axis_d = move.axes_d[ea_index]
- if (not move.axes_d[0] and not move.axes_d[1]) or axis_r < 0.:
+ if (not move.axes_d[0] and not move.axes_d[1]) or axis_r < 0.0:
# Extrude only move (or retraction move) - limit accel and velocity
if abs(axis_d) > self.max_e_dist:
raise self.printer.command_error(
"Extrude only move too long (%.3fmm vs %.3fmm)\n"
"See the 'max_extrude_only_distance' config"
- " option for details" % (axis_d, self.max_e_dist))
- inv_extrude_r = 1. / abs(axis_r)
- move.limit_speed(self.max_e_velocity * inv_extrude_r,
- self.max_e_accel * inv_extrude_r)
+ " option for details" % (axis_d, self.max_e_dist)
+ )
+ inv_extrude_r = 1.0 / abs(axis_r)
+ move.limit_speed(
+ self.max_e_velocity * inv_extrude_r, self.max_e_accel * inv_extrude_r
+ )
elif axis_r > self.max_extrude_ratio:
if axis_d <= self.nozzle_diameter * self.max_extrude_ratio:
# Permit extrusion if amount extruded is tiny
return
area = axis_r * self.filament_area
- logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)",
- axis_r, self.max_extrude_ratio, area, move.move_d)
+ logging.debug(
+ "Overextrude: %s vs %s (area=%.3f dist=%.3f)",
+ axis_r,
+ self.max_extrude_ratio,
+ area,
+ move.move_d,
+ )
raise self.printer.command_error(
"Move exceeds maximum extrusion (%.3fmm^2 vs %.3fmm^2)\n"
"See the 'max_extrude_cross_section' config option for details"
- % (area, self.max_extrude_ratio * self.filament_area))
+ % (area, self.max_extrude_ratio * self.filament_area)
+ )
+
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 process_move(self, print_time, move, ea_index):
axis_r = move.axes_r[ea_index]
accel = move.accel * axis_r
start_v = move.start_v * axis_r
cruise_v = move.cruise_v * axis_r
can_pressure_advance = False
- if axis_r > 0. and (move.axes_d[0] or move.axes_d[1]):
+ if axis_r > 0.0 and (move.axes_d[0] or move.axes_d[1]):
can_pressure_advance = True
# Queue movement (x is extruder movement, y is pressure advance flag)
- self.trapq_append(self.trapq, print_time,
- move.accel_t, move.cruise_t, move.decel_t,
- move.start_pos[ea_index], 0., 0.,
- 1., can_pressure_advance, 0.,
- start_v, cruise_v, accel)
+ self.trapq_append(
+ self.trapq,
+ print_time,
+ move.accel_t,
+ move.cruise_t,
+ move.decel_t,
+ move.start_pos[ea_index],
+ 0.0,
+ 0.0,
+ 1.0,
+ can_pressure_advance,
+ 0.0,
+ start_v,
+ cruise_v,
+ accel,
+ )
self.last_position = move.end_pos[ea_index]
+
def find_past_position(self, print_time):
if self.extruder_stepper is None:
- return 0.
+ return 0.0
return self.extruder_stepper.find_past_position(print_time)
+
def cmd_M104(self, gcmd, wait=False):
# Set Extruder Temperature
- temp = gcmd.get_float('S', 0.)
- index = gcmd.get_int('T', None, minval=0)
+ temp = gcmd.get_float("S", 0.0)
+ index = gcmd.get_int("T", None, minval=0)
if index is not None:
- section = 'extruder'
+ section = "extruder"
if index:
- section = 'extruder%d' % (index,)
+ section = "extruder%d" % (index,)
extruder = self.printer.lookup_object(section, None)
if extruder is None:
- if temp <= 0.:
+ if temp <= 0.0:
return
raise gcmd.error("Extruder not configured")
else:
- extruder = self.printer.lookup_object('toolhead').get_extruder()
- pheaters = self.printer.lookup_object('heaters')
+ extruder = self.printer.lookup_object("toolhead").get_extruder()
+ pheaters = self.printer.lookup_object("heaters")
pheaters.set_temperature(extruder.get_heater(), temp, wait)
+
def cmd_M109(self, gcmd):
# Set Extruder Temperature and Wait
self.cmd_M104(gcmd, wait=True)
+
cmd_ACTIVATE_EXTRUDER_help = "Change the active extruder"
+
def cmd_ACTIVATE_EXTRUDER(self, gcmd):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
if toolhead.get_extruder() is self:
gcmd.respond_info("Extruder %s already active" % (self.name,))
return
@@ -284,31 +363,40 @@ class PrinterExtruder:
toolhead.set_extruder(self, self.last_position)
self.printer.send_event("extruder:activate_extruder")
+
# Dummy extruder class used when a printer has no extruder at all
class DummyExtruder:
def __init__(self, printer):
self.printer = printer
+
def check_move(self, move, ea_index):
raise move.move_error("Extrude when no extruder present")
+
def find_past_position(self, print_time):
- return 0.
+ return 0.0
+
def calc_junction(self, prev_move, move, ea_index):
return move.max_cruise_v2
+
def get_name(self):
return ""
+
def get_heater(self):
raise self.printer.command_error("Extruder not configured")
+
def get_trapq(self):
return None
+
def get_axis_gcode_id(self):
- return 'E'
+ return "E"
+
def add_printer_objects(config):
printer = config.get_printer()
for i in range(99):
- section = 'extruder'
+ section = "extruder"
if i:
- section = 'extruder%d' % (i,)
+ section = "extruder%d" % (i,)
if not config.has_section(section):
break
pe = PrinterExtruder(config.getsection(section), i)
diff --git a/klippy/kinematics/generic_cartesian.py b/klippy/kinematics/generic_cartesian.py
index b8cabb77..a76e4a21 100644
--- a/klippy/kinematics/generic_cartesian.py
+++ b/klippy/kinematics/generic_cartesian.py
@@ -9,6 +9,7 @@ import gcode, mathutil, stepper
from . import idex_modes
from . import kinematic_stepper as ks
+
def mat_mul(a, b):
if len(a[0]) != len(b):
return None
@@ -19,89 +20,118 @@ def mat_mul(a, b):
res[i].append(sum(a[i][k] * b[k][j] for k in range(len(b))))
return res
+
def mat_transp(a):
res = []
for i in range(len(a[0])):
res.append([a[j][i] for j in range(len(a))])
return res
+
def mat_pseudo_inverse(m):
mt = mat_transp(m)
mtm = mat_mul(mt, m)
pinv = mat_mul(mathutil.matrix_inv(mtm), mt)
return pinv
+
class MainCarriage:
def __init__(self, config, axis):
self.rail = stepper.GenericPrinterRail(config)
- self.axis = ord(axis) - ord('x')
+ self.axis = ord(axis) - ord("x")
self.axis_name = axis
self.dual_carriage = None
+
def get_name(self):
return self.rail.get_name(short=True)
+
def get_axis(self):
return self.axis
+
def get_rail(self):
return self.rail
+
def add_stepper(self, kin_stepper):
self.rail.add_stepper(kin_stepper.get_stepper())
+
def is_active(self):
if self.dual_carriage is None:
return True
return self.dual_carriage.get_dc_module().is_active(self.rail)
+
def set_dual_carriage(self, carriage):
old_dc = self.dual_carriage
self.dual_carriage = carriage
return old_dc
+
def get_dual_carriage(self):
return self.dual_carriage
+
class ExtraCarriage:
def __init__(self, config, carriages):
self.name = config.get_name().split()[-1]
- self.primary_carriage = config.getchoice('primary_carriage', carriages)
- self.endstop_pin = config.get('endstop_pin')
+ self.primary_carriage = config.getchoice("primary_carriage", carriages)
+ self.endstop_pin = config.get("endstop_pin")
+
def get_name(self):
return self.name
+
def get_axis(self):
return self.primary_carriage.get_axis()
+
def get_rail(self):
return self.primary_carriage.get_rail()
+
def add_stepper(self, kin_stepper):
- self.get_rail().add_stepper(kin_stepper.get_stepper(),
- self.endstop_pin, self.name)
+ self.get_rail().add_stepper(
+ kin_stepper.get_stepper(), self.endstop_pin, self.name
+ )
+
class DualCarriage:
def __init__(self, config, carriages):
self.printer = config.get_printer()
self.rail = stepper.GenericPrinterRail(config)
- self.primary_carriage = config.getchoice('primary_carriage', carriages)
+ self.primary_carriage = config.getchoice("primary_carriage", carriages)
if self.primary_carriage.set_dual_carriage(self) is not None:
raise config.error(
- "Redefinition of dual_carriage for carriage '%s'" %
- self.primary_carriage.get_name())
+ "Redefinition of dual_carriage for carriage '%s'"
+ % self.primary_carriage.get_name()
+ )
self.axis = self.primary_carriage.get_axis()
if self.axis > 1:
- raise config.error("Invalid axis '%s' for dual_carriage" %
- self.primary_carriage.get_axis_name())
- self.safe_dist = config.getfloat('safe_distance', None, minval=0.)
+ raise config.error(
+ "Invalid axis '%s' for dual_carriage"
+ % self.primary_carriage.get_axis_name()
+ )
+ self.safe_dist = config.getfloat("safe_distance", None, minval=0.0)
+
def get_name(self):
return self.rail.get_name(short=True)
+
def get_axis(self):
return self.primary_carriage.get_axis()
+
def get_rail(self):
return self.rail
+
def get_safe_dist(self):
return self.safe_dist
+
def get_dc_module(self):
- return self.printer.lookup_object('dual_carriage')
+ return self.printer.lookup_object("dual_carriage")
+
def is_active(self):
return self.get_dc_module().is_active(self.rail)
+
def get_dual_carriage(self):
return self.primary_carriage
+
def add_stepper(self, kin_stepper):
self.rail.add_stepper(kin_stepper.get_stepper())
+
class GenericCartesianKinematics:
def __init__(self, toolhead, config):
self.printer = config.get_printer()
@@ -115,42 +145,50 @@ class GenericCartesianKinematics:
primary_rails = [pc.get_rail() for pc in pcs]
dual_rails = [dc.get_rail() for dc in self.dc_carriages]
axes = [dc.get_axis() for dc in self.dc_carriages]
- safe_dist = {dc.get_axis() : dc.get_safe_dist()
- for dc in self.dc_carriages}
+ safe_dist = {dc.get_axis(): dc.get_safe_dist() for dc in self.dc_carriages}
self.dc_module = dc_module = idex_modes.DualCarriages(
- self.printer, primary_rails, dual_rails, axes, safe_dist)
- zero_pos = (0., 0.)
+ self.printer, primary_rails, dual_rails, axes, safe_dist
+ )
+ zero_pos = (0.0, 0.0)
for acs in itertools.product(*zip(pcs, self.dc_carriages)):
for c in acs:
dc_module.get_dc_rail_wrapper(c.get_rail()).activate(
- idex_modes.PRIMARY, zero_pos)
+ idex_modes.PRIMARY, zero_pos
+ )
dc_rail = c.get_dual_carriage().get_rail()
dc_module.get_dc_rail_wrapper(dc_rail).inactivate(zero_pos)
self._check_kinematics(config.error)
for c in pcs:
dc_module.get_dc_rail_wrapper(c.get_rail()).activate(
- idex_modes.PRIMARY, zero_pos)
+ idex_modes.PRIMARY, zero_pos
+ )
dc_rail = c.get_dual_carriage().get_rail()
dc_module.get_dc_rail_wrapper(dc_rail).inactivate(zero_pos)
else:
self._check_kinematics(config.error)
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
- self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
- above=0., maxval=max_velocity)
- self.max_z_accel = config.getfloat('max_z_accel', max_accel,
- above=0., maxval=max_accel)
+ self.max_z_velocity = config.getfloat(
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
+ self.max_z_accel = config.getfloat(
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
self.limits = [(1.0, -1.0)] * 3
# Register gcode commands
- gcode = self.printer.lookup_object('gcode')
- gcode.register_command("SET_STEPPER_CARRIAGES",
- self.cmd_SET_STEPPER_CARRIAGES,
- desc=self.cmd_SET_STEPPER_CARRIAGES_help)
+ gcode = self.printer.lookup_object("gcode")
+ gcode.register_command(
+ "SET_STEPPER_CARRIAGES",
+ self.cmd_SET_STEPPER_CARRIAGES,
+ desc=self.cmd_SET_STEPPER_CARRIAGES_help,
+ )
+
def _load_kinematics(self, config):
- carriages = {a : MainCarriage(config.getsection('carriage ' + a), a)
- for a in 'xyz'}
+ carriages = {
+ a: MainCarriage(config.getsection("carriage " + a), a) for a in "xyz"
+ }
dc_carriages = []
- for c in config.get_prefix_sections('dual_carriage '):
+ for c in config.get_prefix_sections("dual_carriage "):
dc_carriages.append(DualCarriage(c, carriages))
for dc in dc_carriages:
name = dc.get_name()
@@ -160,7 +198,7 @@ class GenericCartesianKinematics:
self.carriages = dict(carriages)
self.dc_carriages = dc_carriages
ec_carriages = []
- for c in config.get_prefix_sections('extra_carriage '):
+ for c in config.get_prefix_sections("extra_carriage "):
ec_carriages.append(ExtraCarriage(c, carriages))
for ec in ec_carriages:
name = ec.get_name()
@@ -171,6 +209,7 @@ class GenericCartesianKinematics:
self.all_carriages = carriages
self._check_carriages_references(config.error)
self._check_multi_mcu_homing(config.error)
+
def _check_carriages_references(self, report_error):
carriages = dict(self.all_carriages)
for s in self.kin_steppers:
@@ -178,21 +217,35 @@ class GenericCartesianKinematics:
carriages.pop(c.get_name(), None)
if carriages:
raise report_error(
- "Carriage(s) %s must be referenced by some "
- "stepper(s)" % (", ".join(carriages),))
+ "Carriage(s) %s must be referenced by some "
+ "stepper(s)" % (", ".join(carriages),)
+ )
+
def _check_multi_mcu_homing(self, report_error):
for carriage in self.carriages.values():
for es in carriage.get_rail().get_endstops():
- stepper_mcus = set([s.get_mcu() for s in es[0].get_steppers()
- if s in carriage.get_rail().get_steppers()])
+ stepper_mcus = set(
+ [
+ s.get_mcu()
+ for s in es[0].get_steppers()
+ if s in carriage.get_rail().get_steppers()
+ ]
+ )
if len(stepper_mcus) > 1:
- raise report_error("Multi-mcu homing not supported on"
- " multi-mcu shared carriage %s" % es[1])
+ raise report_error(
+ "Multi-mcu homing not supported on"
+ " multi-mcu shared carriage %s" % es[1]
+ )
+
def _load_steppers(self, config, carriages):
- return [ks.KinematicStepper(c, carriages)
- for c in config.get_prefix_sections('stepper ')]
+ return [
+ ks.KinematicStepper(c, carriages)
+ for c in config.get_prefix_sections("stepper ")
+ ]
+
def get_steppers(self):
return [s.get_stepper() for s in self.kin_steppers]
+
def get_primary_carriages(self):
carriages = [self.carriages[a] for a in "xyz"]
if self.dc_module:
@@ -202,13 +255,15 @@ class GenericCartesianKinematics:
if c.get_rail() == primary_rail:
carriages[a] = c
return carriages
+
def _get_kinematics_coeffs(self):
- matr = {s.get_name() : list(s.get_kin_coeffs())
- for s in self.kin_steppers}
- offs = {s.get_name() : [0.] * 3 for s in self.kin_steppers}
+ matr = {s.get_name(): list(s.get_kin_coeffs()) for s in self.kin_steppers}
+ offs = {s.get_name(): [0.0] * 3 for s in self.kin_steppers}
if self.dc_module is None:
- return ([matr[s.get_name()] for s in self.kin_steppers],
- [0. for s in self.kin_steppers])
+ return (
+ [matr[s.get_name()] for s in self.kin_steppers],
+ [0.0 for s in self.kin_steppers],
+ )
axes = [dc.get_axis() for dc in self.dc_carriages]
orig_matr = copy.deepcopy(matr)
for dc in self.dc_carriages:
@@ -218,33 +273,41 @@ class GenericCartesianKinematics:
for s in c.get_rail().get_steppers():
matr[s.get_name()][axis] *= m
offs[s.get_name()][axis] += o
- return ([matr[s.get_name()] for s in self.kin_steppers],
- [mathutil.matrix_dot(orig_matr[s.get_name()],
- offs[s.get_name()])
- for s in self.kin_steppers])
+ return (
+ [matr[s.get_name()] for s in self.kin_steppers],
+ [
+ mathutil.matrix_dot(orig_matr[s.get_name()], offs[s.get_name()])
+ for s in self.kin_steppers
+ ],
+ )
+
def _check_kinematics(self, report_error):
matr, _ = self._get_kinematics_coeffs()
det = mathutil.matrix_det(mat_mul(mat_transp(matr), matr))
if abs(det) < 0.00001:
raise report_error(
- "Verify configured stepper(s) and their 'carriages' "
- "specifications, the current configuration does not "
- "allow independent movements of all printer axes.")
+ "Verify configured stepper(s) and their 'carriages' "
+ "specifications, the current configuration does not "
+ "allow independent movements of all printer axes."
+ )
+
def calc_position(self, stepper_positions):
matr, offs = self._get_kinematics_coeffs()
spos = [stepper_positions[s.get_name()] for s in self.kin_steppers]
pinv = mat_pseudo_inverse(matr)
- pos = mat_mul([[sp-o for sp, o in zip(spos, offs)]], mat_transp(pinv))
+ pos = mat_mul([[sp - o for sp, o in zip(spos, offs)]], mat_transp(pinv))
for i in range(3):
if not any(pinv[i]):
pos[0][i] = None
return pos[0]
+
def update_limits(self, i, range):
l, h = self.limits[i]
# Only update limits if this axis was already homed,
# otherwise leave in un-homed state.
if l <= h:
self.limits[i] = range
+
def set_position(self, newpos, homing_axes):
for s in self.kin_steppers:
s.set_position(newpos)
@@ -254,10 +317,12 @@ class GenericCartesianKinematics:
if c.get_axis() == axis and c.is_active():
self.limits[axis] = c.get_rail().get_range()
break
+
def clear_homing_state(self, clear_axes):
for axis, axis_name in enumerate("xyz"):
if axis_name in clear_axes:
self.limits[axis] = (1.0, -1.0)
+
def home_axis(self, homing_state, axis, rail):
# Determine movement
position_min, position_max = rail.get_range()
@@ -271,6 +336,7 @@ class GenericCartesianKinematics:
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
+
def home(self, homing_state):
self._check_kinematics(self.printer.command_error)
# Each axis is homed independently and in order
@@ -280,20 +346,26 @@ class GenericCartesianKinematics:
self.dc_module.home(homing_state, axis)
else:
self.home_axis(homing_state, axis, carriage.get_rail())
+
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
- if (move.axes_d[i]
- and (end_pos[i] < self.limits[i][0]
- or end_pos[i] > self.limits[i][1])):
+ if move.axes_d[i] and (
+ end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1]
+ ):
if self.limits[i][0] > self.limits[i][1]:
raise move.move_error("Must home axis first")
raise move.move_error()
+
def check_move(self, move):
limits = self.limits
xpos, ypos = move.end_pos[:2]
- if (xpos < limits[0][0] or xpos > limits[0][1]
- or ypos < limits[1][0] or ypos > limits[1][1]):
+ if (
+ xpos < limits[0][0]
+ or xpos > limits[0][1]
+ or ypos < limits[1][0]
+ or ypos > limits[1][1]
+ ):
self._check_endstops(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
@@ -301,27 +373,31 @@ class GenericCartesianKinematics:
# Move with Z - update velocity and accel for slower Z axis
self._check_endstops(move)
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(
- self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+
def get_status(self, eventtime):
axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h]
- ranges = [c.get_rail().get_range()
- for c in self.get_primary_carriages()]
- axes_min = gcode.Coord(*[r[0] for r in ranges], e=0.)
- axes_max = gcode.Coord(*[r[1] for r in ranges], e=0.)
+ ranges = [c.get_rail().get_range() for c in self.get_primary_carriages()]
+ axes_min = gcode.Coord(*[r[0] for r in ranges], e=0.0)
+ axes_max = gcode.Coord(*[r[1] for r in ranges], e=0.0)
return {
- 'homed_axes': "".join(axes),
- 'axis_minimum': axes_min,
- 'axis_maximum': axes_max,
+ "homed_axes": "".join(axes),
+ "axis_minimum": axes_min,
+ "axis_maximum": axes_max,
}
+
cmd_SET_STEPPER_CARRIAGES_help = "Set stepper carriages"
+
def cmd_SET_STEPPER_CARRIAGES(self, gcmd):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
stepper_name = gcmd.get("STEPPER")
- steppers = [stepper for stepper in self.kin_steppers
- if stepper.get_name() == stepper_name
- or stepper.get_name(short=True) == stepper_name]
+ steppers = [
+ stepper
+ for stepper in self.kin_steppers
+ if stepper.get_name() == stepper_name
+ or stepper.get_name(short=True) == stepper_name
+ ]
if len(steppers) != 1:
raise gcmd.error("Invalid STEPPER '%s' specified" % stepper_name)
stepper = steppers[0]
@@ -333,8 +409,10 @@ class GenericCartesianKinematics:
new_carriages = stepper.get_carriages()
if new_carriages != old_carriages:
stepper.update_kin_coeffs(old_kin_coeffs)
- raise gcmd.error("SET_STEPPER_CARRIAGES cannot add or remove "
- "carriages that the stepper controls")
+ raise gcmd.error(
+ "SET_STEPPER_CARRIAGES cannot add or remove "
+ "carriages that the stepper controls"
+ )
pos = toolhead.get_position()
stepper.set_position(pos)
if not validate:
@@ -346,13 +424,16 @@ class GenericCartesianKinematics:
for acs in itertools.product(*zip(pcs, self.dc_carriages)):
for c in acs:
self.dc_module.get_dc_rail_wrapper(c.get_rail()).activate(
- idex_modes.PRIMARY, pos)
+ idex_modes.PRIMARY, pos
+ )
self.dc_module.get_dc_rail_wrapper(
- c.get_dual_carriage().get_rail()).inactivate(pos)
+ c.get_dual_carriage().get_rail()
+ ).inactivate(pos)
self._check_kinematics(gcmd.error)
self.dc_module.restore_dual_carriage_state(dc_state, move=0)
else:
self._check_kinematics(gcmd.error)
+
def load_kinematics(toolhead, config):
return GenericCartesianKinematics(toolhead, config)
diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py
index fd2621d5..2562ba33 100644
--- a/klippy/kinematics/hybrid_corexy.py
+++ b/klippy/kinematics/hybrid_corexy.py
@@ -7,61 +7,75 @@ import logging
import stepper
from . import idex_modes
+
# The hybrid-corexy kinematic is also known as Markforged kinematics
class HybridCoreXYKinematics:
def __init__(self, toolhead, config):
self.printer = config.get_printer()
# itersolve parameters
- self.rails = [ stepper.LookupRail(config.getsection('stepper_x')),
- stepper.LookupMultiRail(config.getsection('stepper_y')),
- stepper.LookupMultiRail(config.getsection('stepper_z'))]
- self.rails[1].get_endstops()[0][0].add_stepper(
- self.rails[0].get_steppers()[0])
- self.rails[0].setup_itersolve('corexy_stepper_alloc', b'-')
- self.rails[1].setup_itersolve('cartesian_stepper_alloc', b'y')
- self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z')
+ self.rails = [
+ stepper.LookupRail(config.getsection("stepper_x")),
+ stepper.LookupMultiRail(config.getsection("stepper_y")),
+ stepper.LookupMultiRail(config.getsection("stepper_z")),
+ ]
+ self.rails[1].get_endstops()[0][0].add_stepper(self.rails[0].get_steppers()[0])
+ self.rails[0].setup_itersolve("corexy_stepper_alloc", b"-")
+ self.rails[1].setup_itersolve("cartesian_stepper_alloc", b"y")
+ self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"z")
ranges = [r.get_range() for r in self.rails]
- self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
- self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
+ self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0)
+ self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0)
self.dc_module = None
- if config.has_section('dual_carriage'):
- dc_config = config.getsection('dual_carriage')
+ if config.has_section("dual_carriage"):
+ dc_config = config.getsection("dual_carriage")
# dummy for cartesian config users
- dc_config.getchoice('axis', ['x'], default='x')
+ dc_config.getchoice("axis", ["x"], default="x")
# setup second dual carriage rail
self.rails.append(stepper.LookupRail(dc_config))
self.rails[1].get_endstops()[0][0].add_stepper(
- self.rails[3].get_steppers()[0])
- self.rails[3].setup_itersolve('corexy_stepper_alloc', b'+')
+ self.rails[3].get_steppers()[0]
+ )
+ self.rails[3].setup_itersolve("corexy_stepper_alloc", b"+")
self.dc_module = idex_modes.DualCarriages(
- self.printer, [self.rails[0]], [self.rails[3]], axes=[0],
- safe_dist=dc_config.getfloat(
- 'safe_distance', None, minval=0.))
+ self.printer,
+ [self.rails[0]],
+ [self.rails[3]],
+ axes=[0],
+ safe_dist=dc_config.getfloat("safe_distance", None, minval=0.0),
+ )
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat(
- 'max_z_velocity', max_velocity, above=0., maxval=max_velocity)
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
self.max_z_accel = config.getfloat(
- 'max_z_accel', max_accel, above=0., maxval=max_accel)
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
self.limits = [(1.0, -1.0)] * 3
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
- if (self.dc_module is not None and 'PRIMARY' == \
- self.dc_module.get_status()['carriage_1']):
+ if (
+ self.dc_module is not None
+ and "PRIMARY" == self.dc_module.get_status()["carriage_1"]
+ ):
return [pos[3] - pos[1], pos[1], pos[2]]
else:
return [pos[0] + pos[1], pos[1], pos[2]]
+
def update_limits(self, i, range):
l, h = self.limits[i]
# Only update limits if this axis was already homed,
# otherwise leave in un-homed state.
if l <= h:
self.limits[i] = range
+
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):
rail.set_position(newpos)
@@ -72,10 +86,12 @@ class HybridCoreXYKinematics:
else:
rail = self.rails[axis]
self.limits[axis] = rail.get_range()
+
def clear_homing_state(self, clear_axes):
for axis, axis_name in enumerate("xyz"):
if axis_name in clear_axes:
self.limits[axis] = (1.0, -1.0)
+
def home_axis(self, homing_state, axis, rail):
position_min, position_max = rail.get_range()
hi = rail.get_homing_info()
@@ -88,26 +104,33 @@ class HybridCoreXYKinematics:
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
+
def home(self, homing_state):
for axis in homing_state.get_axes():
if self.dc_module is not None and axis == 0:
self.dc_module.home(homing_state, axis)
else:
self.home_axis(homing_state, axis, self.rails[axis])
+
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
- if (move.axes_d[i]
- and (end_pos[i] < self.limits[i][0]
- or end_pos[i] > self.limits[i][1])):
+ if move.axes_d[i] and (
+ end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1]
+ ):
if self.limits[i][0] > self.limits[i][1]:
raise move.move_error("Must home axis first")
raise move.move_error()
+
def check_move(self, move):
limits = self.limits
xpos, ypos = move.end_pos[:2]
- if (xpos < limits[0][0] or xpos > limits[0][1]
- or ypos < limits[1][0] or ypos > limits[1][1]):
+ if (
+ xpos < limits[0][0]
+ or xpos > limits[0][1]
+ or ypos < limits[1][0]
+ or ypos > limits[1][1]
+ ):
self._check_endstops(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
@@ -115,15 +138,16 @@ class HybridCoreXYKinematics:
# Move with Z - update velocity and accel for slower Z axis
self._check_endstops(move)
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(
- self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+
def get_status(self, eventtime):
axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h]
return {
- 'homed_axes': "".join(axes),
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max
+ "homed_axes": "".join(axes),
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return HybridCoreXYKinematics(toolhead, config)
diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py
index b9699982..6c3ae57d 100644
--- a/klippy/kinematics/hybrid_corexz.py
+++ b/klippy/kinematics/hybrid_corexz.py
@@ -7,61 +7,75 @@ import logging
import stepper
from . import idex_modes
+
# The hybrid-corexz kinematic is also known as Markforged kinematics
class HybridCoreXZKinematics:
def __init__(self, toolhead, config):
self.printer = config.get_printer()
# itersolve parameters
- self.rails = [ stepper.LookupRail(config.getsection('stepper_x')),
- stepper.LookupMultiRail(config.getsection('stepper_y')),
- stepper.LookupMultiRail(config.getsection('stepper_z'))]
- self.rails[2].get_endstops()[0][0].add_stepper(
- self.rails[0].get_steppers()[0])
- self.rails[0].setup_itersolve('corexz_stepper_alloc', b'-')
- self.rails[1].setup_itersolve('cartesian_stepper_alloc', b'y')
- self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z')
+ self.rails = [
+ stepper.LookupRail(config.getsection("stepper_x")),
+ stepper.LookupMultiRail(config.getsection("stepper_y")),
+ stepper.LookupMultiRail(config.getsection("stepper_z")),
+ ]
+ self.rails[2].get_endstops()[0][0].add_stepper(self.rails[0].get_steppers()[0])
+ self.rails[0].setup_itersolve("corexz_stepper_alloc", b"-")
+ self.rails[1].setup_itersolve("cartesian_stepper_alloc", b"y")
+ self.rails[2].setup_itersolve("cartesian_stepper_alloc", b"z")
ranges = [r.get_range() for r in self.rails]
- self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
- self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
+ self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.0)
+ self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0)
self.dc_module = None
- if config.has_section('dual_carriage'):
- dc_config = config.getsection('dual_carriage')
+ if config.has_section("dual_carriage"):
+ dc_config = config.getsection("dual_carriage")
# dummy for cartesian config users
- dc_config.getchoice('axis', ['x'], default='x')
+ dc_config.getchoice("axis", ["x"], default="x")
# setup second dual carriage rail
self.rails.append(stepper.LookupRail(dc_config))
self.rails[2].get_endstops()[0][0].add_stepper(
- self.rails[3].get_steppers()[0])
- self.rails[3].setup_itersolve('corexz_stepper_alloc', b'+')
+ self.rails[3].get_steppers()[0]
+ )
+ self.rails[3].setup_itersolve("corexz_stepper_alloc", b"+")
self.dc_module = idex_modes.DualCarriages(
- self.printer, [self.rails[0]], [self.rails[3]], axes=[0],
- safe_dist=dc_config.getfloat(
- 'safe_distance', None, minval=0.))
+ self.printer,
+ [self.rails[0]],
+ [self.rails[3]],
+ axes=[0],
+ safe_dist=dc_config.getfloat("safe_distance", None, minval=0.0),
+ )
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat(
- 'max_z_velocity', max_velocity, above=0., maxval=max_velocity)
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
self.max_z_accel = config.getfloat(
- 'max_z_accel', max_accel, above=0., maxval=max_accel)
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
self.limits = [(1.0, -1.0)] * 3
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
- if (self.dc_module is not None and 'PRIMARY' == \
- self.dc_module.get_status()['carriage_1']):
+ if (
+ self.dc_module is not None
+ and "PRIMARY" == self.dc_module.get_status()["carriage_1"]
+ ):
return [pos[3] - pos[2], pos[1], pos[2]]
else:
return [pos[0] + pos[2], pos[1], pos[2]]
+
def update_limits(self, i, range):
l, h = self.limits[i]
# Only update limits if this axis was already homed,
# otherwise leave in un-homed state.
if l <= h:
self.limits[i] = range
+
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):
rail.set_position(newpos)
@@ -72,10 +86,12 @@ class HybridCoreXZKinematics:
else:
rail = self.rails[axis]
self.limits[axis] = rail.get_range()
+
def clear_homing_state(self, clear_axes):
for axis, axis_name in enumerate("xyz"):
if axis_name in clear_axes:
self.limits[axis] = (1.0, -1.0)
+
def home_axis(self, homing_state, axis, rail):
position_min, position_max = rail.get_range()
hi = rail.get_homing_info()
@@ -88,26 +104,33 @@ class HybridCoreXZKinematics:
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
+
def home(self, homing_state):
for axis in homing_state.get_axes():
if self.dc_module is not None and axis == 0:
self.dc_module.home(homing_state, axis)
else:
self.home_axis(homing_state, axis, self.rails[axis])
+
def _check_endstops(self, move):
end_pos = move.end_pos
for i in (0, 1, 2):
- if (move.axes_d[i]
- and (end_pos[i] < self.limits[i][0]
- or end_pos[i] > self.limits[i][1])):
+ if move.axes_d[i] and (
+ end_pos[i] < self.limits[i][0] or end_pos[i] > self.limits[i][1]
+ ):
if self.limits[i][0] > self.limits[i][1]:
raise move.move_error("Must home axis first")
raise move.move_error()
+
def check_move(self, move):
limits = self.limits
xpos, ypos = move.end_pos[:2]
- if (xpos < limits[0][0] or xpos > limits[0][1]
- or ypos < limits[1][0] or ypos > limits[1][1]):
+ if (
+ xpos < limits[0][0]
+ or xpos > limits[0][1]
+ or ypos < limits[1][0]
+ or ypos > limits[1][1]
+ ):
self._check_endstops(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
@@ -115,15 +138,16 @@ class HybridCoreXZKinematics:
# Move with Z - update velocity and accel for slower Z axis
self._check_endstops(move)
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(
- self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+
def get_status(self, eventtime):
axes = [a for a, (l, h) in zip("xyz", self.limits) if l <= h]
return {
- 'homed_axes': "".join(axes),
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max
+ "homed_axes": "".join(axes),
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return HybridCoreXZKinematics(toolhead, config)
diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py
index 19d5a655..5910d5c0 100644
--- a/klippy/kinematics/idex_modes.py
+++ b/klippy/kinematics/idex_modes.py
@@ -7,29 +7,33 @@
import collections, logging, math
import chelper
-INACTIVE = 'INACTIVE'
-PRIMARY = 'PRIMARY'
-COPY = 'COPY'
-MIRROR = 'MIRROR'
+INACTIVE = "INACTIVE"
+PRIMARY = "PRIMARY"
+COPY = "COPY"
+MIRROR = "MIRROR"
+
class DualCarriages:
VALID_MODES = [PRIMARY, COPY, MIRROR]
- def __init__(self, printer, primary_rails, dual_rails, axes,
- safe_dist={}):
+
+ def __init__(self, printer, primary_rails, dual_rails, axes, safe_dist={}):
self.printer = printer
self.axes = axes
self._init_steppers(primary_rails + dual_rails)
self.primary_rails = [
- DualCarriagesRail(printer, c, dual_rails[i],
- axes[i], active=True)
- for i, c in enumerate(primary_rails)]
+ DualCarriagesRail(printer, c, dual_rails[i], axes[i], active=True)
+ for i, c in enumerate(primary_rails)
+ ]
self.dual_rails = [
- DualCarriagesRail(printer, c, primary_rails[i],
- axes[i], active=False)
- for i, c in enumerate(dual_rails)]
+ DualCarriagesRail(printer, c, primary_rails[i], axes[i], active=False)
+ for i, c in enumerate(dual_rails)
+ ]
self.dc_rails = collections.OrderedDict(
- [(c.rail.get_name(short=True), c)
- for c in self.primary_rails + self.dual_rails])
+ [
+ (c.rail.get_name(short=True), c)
+ for c in self.primary_rails + self.dual_rails
+ ]
+ )
self.saved_states = {}
self.safe_dist = {}
for i, dc in enumerate(dual_rails):
@@ -42,22 +46,29 @@ class DualCarriages:
self.safe_dist[axis] = safe_dist
continue
pc = primary_rails[i]
- self.safe_dist[axis] = min(abs(pc.position_min - dc.position_min),
- abs(pc.position_max - dc.position_max))
- self.printer.add_object('dual_carriage', self)
+ self.safe_dist[axis] = min(
+ abs(pc.position_min - dc.position_min),
+ abs(pc.position_max - dc.position_max),
+ )
+ self.printer.add_object("dual_carriage", self)
self.printer.register_event_handler("klippy:ready", self._handle_ready)
- gcode = self.printer.lookup_object('gcode')
+ gcode = self.printer.lookup_object("gcode")
gcode.register_command(
- 'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE,
- desc=self.cmd_SET_DUAL_CARRIAGE_help)
+ "SET_DUAL_CARRIAGE",
+ self.cmd_SET_DUAL_CARRIAGE,
+ desc=self.cmd_SET_DUAL_CARRIAGE_help,
+ )
gcode.register_command(
- 'SAVE_DUAL_CARRIAGE_STATE',
- self.cmd_SAVE_DUAL_CARRIAGE_STATE,
- desc=self.cmd_SAVE_DUAL_CARRIAGE_STATE_help)
+ "SAVE_DUAL_CARRIAGE_STATE",
+ self.cmd_SAVE_DUAL_CARRIAGE_STATE,
+ desc=self.cmd_SAVE_DUAL_CARRIAGE_STATE_help,
+ )
gcode.register_command(
- 'RESTORE_DUAL_CARRIAGE_STATE',
- self.cmd_RESTORE_DUAL_CARRIAGE_STATE,
- desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help)
+ "RESTORE_DUAL_CARRIAGE_STATE",
+ self.cmd_RESTORE_DUAL_CARRIAGE_STATE,
+ desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help,
+ )
+
def _init_steppers(self, rails):
ffi_main, ffi_lib = chelper.get_ffi()
self.dc_stepper_kinematics = []
@@ -67,8 +78,9 @@ class DualCarriages:
c_steppers = rail.get_steppers()
if not c_steppers:
raise self.printer.config_error(
- "At least one stepper must be "
- "associated with carriage: %s" % rail.get_name())
+ "At least one stepper must be "
+ "associated with carriage: %s" % rail.get_name()
+ )
steppers.update(c_steppers)
for s in steppers:
sk = ffi_main.gc(ffi_lib.dual_carriage_alloc(), ffi_lib.free)
@@ -77,28 +89,34 @@ class DualCarriages:
self.dc_stepper_kinematics.append(sk)
self.orig_stepper_kinematics.append(orig_sk)
s.set_stepper_kinematics(sk)
+
def get_axes(self):
return self.axes
+
def get_primary_rail(self, axis):
for dc_rail in self.dc_rails.values():
if dc_rail.mode == PRIMARY and dc_rail.axis == axis:
return dc_rail.rail
return None
+
def get_dc_rail_wrapper(self, rail):
for dc_rail in self.dc_rails.values():
if dc_rail.rail == rail:
return dc_rail
return None
+
def get_transform(self, rail):
dc_rail = self.get_dc_rail_wrapper(rail)
if dc_rail is not None:
return (dc_rail.scale, dc_rail.offset)
- return (0., 0.)
+ return (0.0, 0.0)
+
def is_active(self, rail):
dc_rail = self.get_dc_rail_wrapper(rail)
return dc_rail.is_active() if dc_rail is not None else False
+
def toggle_active_dc_rail(self, target_dc):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
pos = toolhead.get_position()
kin = toolhead.get_kinematics()
@@ -107,16 +125,17 @@ class DualCarriages:
if dc != target_dc and dc.axis == axis and dc.is_active():
dc.inactivate(pos)
if target_dc.mode != PRIMARY:
- newpos = pos[:axis] + [target_dc.get_axis_position(pos)] \
- + pos[axis+1:]
+ newpos = pos[:axis] + [target_dc.get_axis_position(pos)] + pos[axis + 1 :]
target_dc.activate(PRIMARY, newpos, old_position=pos)
toolhead.set_position(newpos)
kin.update_limits(axis, target_dc.rail.get_range())
+
def home(self, homing_state, axis):
- kin = self.printer.lookup_object('toolhead').get_kinematics()
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis]
- if (self.get_dc_order(dcs[0], dcs[1]) > 0) != \
- dcs[0].rail.get_homing_info().positive_dir:
+ if (self.get_dc_order(dcs[0], dcs[1]) > 0) != dcs[
+ 0
+ ].rail.get_homing_info().positive_dir:
# The second carriage must home first, because the carriages home in
# the same direction and the first carriage homes on the second one
dcs.reverse()
@@ -125,13 +144,20 @@ class DualCarriages:
kin.home_axis(homing_state, axis, dc.rail)
# Restore the original rails ordering
self.toggle_active_dc_rail(dcs[0])
+
def get_status(self, eventtime=None):
- status = {'carriages' : {dc.get_name() : dc.mode
- for dc in self.dc_rails.values()}}
+ status = {
+ "carriages": {dc.get_name(): dc.mode for dc in self.dc_rails.values()}
+ }
if len(self.dc_rails) == 2:
- status.update({('carriage_%d' % (i,)) : dc.mode
- for i, dc in enumerate(self.dc_rails.values())})
+ status.update(
+ {
+ ("carriage_%d" % (i,)): dc.mode
+ for i, dc in enumerate(self.dc_rails.values())
+ }
+ )
return status
+
def get_kin_range(self, toolhead, mode, axis):
pos = toolhead.get_position()
dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis]
@@ -149,21 +175,19 @@ class DualCarriages:
return (range_min, range_max)
if mode == COPY:
- range_min = max(range_min,
- axes_pos[0] - axes_pos[1] + dc1_rail.position_min)
- range_max = min(range_max,
- axes_pos[0] - axes_pos[1] + dc1_rail.position_max)
+ range_min = max(
+ range_min, axes_pos[0] - axes_pos[1] + dc1_rail.position_min
+ )
+ range_max = min(
+ range_max, axes_pos[0] - axes_pos[1] + dc1_rail.position_max
+ )
elif mode == MIRROR:
if self.get_dc_order(dcs[0], dcs[1]) > 0:
- range_min = max(range_min,
- 0.5 * (sum(axes_pos) + safe_dist))
- range_max = min(range_max,
- sum(axes_pos) - dc1_rail.position_min)
+ range_min = max(range_min, 0.5 * (sum(axes_pos) + safe_dist))
+ range_max = min(range_max, sum(axes_pos) - dc1_rail.position_min)
else:
- range_max = min(range_max,
- 0.5 * (sum(axes_pos) - safe_dist))
- range_min = max(range_min,
- sum(axes_pos) - dc1_rail.position_max)
+ range_max = min(range_max, 0.5 * (sum(axes_pos) - safe_dist))
+ range_min = max(range_min, sum(axes_pos) - dc1_rail.position_max)
else:
# mode == PRIMARY
active_idx = 1 if dcs[1].is_active() else 0
@@ -184,6 +208,7 @@ class DualCarriages:
# which actually permits carriage motion.
return (range_min, range_min)
return (range_min, range_max)
+
def get_dc_order(self, first_dc, second_dc):
if first_dc == second_dc:
return 0
@@ -201,8 +226,9 @@ class DualCarriages:
if first_rail.position_endstop > second_rail.position_endstop:
return 1
return -1
+
def activate_dc_mode(self, dc, mode):
- toolhead = self.printer.lookup_object('toolhead')
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
kin = toolhead.get_kinematics()
axis = dc.axis
@@ -214,14 +240,17 @@ class DualCarriages:
self.toggle_active_dc_rail(self.get_dc_rail_wrapper(dc.dual_rail))
dc.activate(mode, toolhead.get_position())
kin.update_limits(axis, self.get_kin_range(toolhead, mode, axis))
+
def _handle_ready(self):
for dc_rail in self.dc_rails.values():
dc_rail.apply_transform()
+
cmd_SET_DUAL_CARRIAGE_help = "Configure the dual carriages mode"
+
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
- carriage_str = gcmd.get('CARRIAGE', None)
+ carriage_str = gcmd.get("CARRIAGE", None)
if carriage_str is None:
- raise gcmd.error('CARRIAGE must be specified')
+ raise gcmd.error("CARRIAGE must be specified")
if carriage_str in self.dc_rails:
dc_rail = self.dc_rails[carriage_str]
else:
@@ -230,65 +259,75 @@ class DualCarriages:
try:
index = int(carriage_str.strip())
if index < 0 or index > 1:
- raise gcmd.error('Invalid CARRIAGE=%d index' % index)
- dc_rail = (self.dual_rails if index
- else self.primary_rails)[0]
+ raise gcmd.error("Invalid CARRIAGE=%d index" % index)
+ dc_rail = (self.dual_rails if index else self.primary_rails)[0]
except ValueError:
pass
if dc_rail is None:
- raise gcmd.error('Invalid CARRIAGE=%s specified' % carriage_str)
- mode = gcmd.get('MODE', PRIMARY).upper()
+ raise gcmd.error("Invalid CARRIAGE=%s specified" % carriage_str)
+ mode = gcmd.get("MODE", PRIMARY).upper()
if mode not in self.VALID_MODES:
raise gcmd.error("Invalid mode=%s specified" % (mode,))
if mode in [COPY, MIRROR]:
if dc_rail in self.primary_rails:
raise gcmd.error(
- "Mode=%s is not supported for carriage=%s" % (
- mode, dc_rail.get_name()))
+ "Mode=%s is not supported for carriage=%s"
+ % (mode, dc_rail.get_name())
+ )
curtime = self.printer.get_reactor().monotonic()
- kin = self.printer.lookup_object('toolhead').get_kinematics()
- axis = 'xyz'[dc_rail.axis]
- if axis not in kin.get_status(curtime)['homed_axes']:
+ kin = self.printer.lookup_object("toolhead").get_kinematics()
+ axis = "xyz"[dc_rail.axis]
+ if axis not in kin.get_status(curtime)["homed_axes"]:
raise gcmd.error(
- "Axis %s must be homed prior to enabling mode=%s" %
- (axis.upper(), mode))
+ "Axis %s must be homed prior to enabling mode=%s"
+ % (axis.upper(), mode)
+ )
self.activate_dc_mode(dc_rail, mode)
- cmd_SAVE_DUAL_CARRIAGE_STATE_help = \
- "Save dual carriages modes and positions"
+
+ cmd_SAVE_DUAL_CARRIAGE_STATE_help = "Save dual carriages modes and positions"
+
def cmd_SAVE_DUAL_CARRIAGE_STATE(self, gcmd):
- state_name = gcmd.get('NAME', 'default')
+ state_name = gcmd.get("NAME", "default")
self.saved_states[state_name] = self.save_dual_carriage_state()
+
def save_dual_carriage_state(self):
- pos = self.printer.lookup_object('toolhead').get_position()
- return {'carriage_modes': {dc.get_name() : dc.mode
- for dc in self.dc_rails.values()},
- 'carriage_positions': {dc.get_name() : dc.get_axis_position(pos)
- for dc in self.dc_rails.values()}}
- cmd_RESTORE_DUAL_CARRIAGE_STATE_help = \
- "Restore dual carriages modes and positions"
+ pos = self.printer.lookup_object("toolhead").get_position()
+ return {
+ "carriage_modes": {dc.get_name(): dc.mode for dc in self.dc_rails.values()},
+ "carriage_positions": {
+ dc.get_name(): dc.get_axis_position(pos)
+ for dc in self.dc_rails.values()
+ },
+ }
+
+ cmd_RESTORE_DUAL_CARRIAGE_STATE_help = "Restore dual carriages modes and positions"
+
def cmd_RESTORE_DUAL_CARRIAGE_STATE(self, gcmd):
- state_name = gcmd.get('NAME', 'default')
+ state_name = gcmd.get("NAME", "default")
saved_state = self.saved_states.get(state_name)
if saved_state is None:
raise gcmd.error("Unknown DUAL_CARRIAGE state: %s" % (state_name,))
- move_speed = gcmd.get_float('MOVE_SPEED', 0., above=0.)
- move = gcmd.get_int('MOVE', 1)
+ move_speed = gcmd.get_float("MOVE_SPEED", 0.0, above=0.0)
+ move = gcmd.get_int("MOVE", 1)
self.restore_dual_carriage_state(saved_state, move, move_speed)
- def restore_dual_carriage_state(self, saved_state, move, move_speed=0.):
- toolhead = self.printer.lookup_object('toolhead')
+
+ def restore_dual_carriage_state(self, saved_state, move, move_speed=0.0):
+ toolhead = self.printer.lookup_object("toolhead")
toolhead.flush_step_generation()
if move:
- homing_speed = 99999999.
+ homing_speed = 99999999.0
move_pos = list(toolhead.get_position())
cur_pos = []
- carriage_positions = saved_state['carriage_positions']
+ carriage_positions = saved_state["carriage_positions"]
dcs = list(self.dc_rails.values())
for dc in dcs:
self.toggle_active_dc_rail(dc)
homing_speed = min(homing_speed, dc.rail.homing_speed)
cur_pos.append(toolhead.get_position())
- dl = [carriage_positions[dc.get_name()] - cur_pos[i][dc.axis]
- for i, dc in enumerate(dcs)]
+ dl = [
+ carriage_positions[dc.get_name()] - cur_pos[i][dc.axis]
+ for i, dc in enumerate(dcs)
+ ]
for axis in self.axes:
dc_ind = [i for i, dc in enumerate(dcs) if dc.axis == axis]
if abs(dl[dc_ind[0]]) >= abs(dl[dc_ind[1]]):
@@ -298,26 +337,29 @@ class DualCarriages:
primary_dc = dcs[primary_ind]
self.toggle_active_dc_rail(primary_dc)
move_pos[axis] = carriage_positions[primary_dc.get_name()]
- dc_mode = INACTIVE if min(abs(dl[primary_ind]),
- abs(dl[secondary_ind])) < .000000001 \
- else COPY if dl[primary_ind] * dl[secondary_ind] > 0 \
- else MIRROR
+ dc_mode = (
+ INACTIVE
+ if min(abs(dl[primary_ind]), abs(dl[secondary_ind])) < 0.000000001
+ else COPY if dl[primary_ind] * dl[secondary_ind] > 0 else MIRROR
+ )
if dc_mode != INACTIVE:
dcs[secondary_ind].activate(dc_mode, cur_pos[primary_ind])
dcs[secondary_ind].override_axis_scaling(
- abs(dl[secondary_ind] / dl[primary_ind]),
- cur_pos[primary_ind])
+ abs(dl[secondary_ind] / dl[primary_ind]), cur_pos[primary_ind]
+ )
toolhead.manual_move(move_pos, move_speed or homing_speed)
toolhead.flush_step_generation()
# Make sure the scaling coefficients are restored with the mode
for dc in dcs:
dc.inactivate(move_pos)
for dc in self.dc_rails.values():
- saved_mode = saved_state['carriage_modes'][dc.get_name()]
+ saved_mode = saved_state["carriage_modes"][dc.get_name()]
self.activate_dc_mode(dc, saved_mode)
+
class DualCarriagesRail:
- ENC_AXES = [b'x', b'y']
+ ENC_AXES = [b"x", b"y"]
+
def __init__(self, printer, rail, dual_rail, axis, active):
self.printer = printer
self.rail = rail
@@ -325,31 +367,39 @@ class DualCarriagesRail:
self.sks = [s.get_stepper_kinematics() for s in rail.get_steppers()]
self.axis = axis
self.mode = (INACTIVE, PRIMARY)[active]
- self.offset = 0.
- self.scale = 1. if active else 0.
+ self.offset = 0.0
+ self.scale = 1.0 if active else 0.0
+
def get_name(self):
return self.rail.get_name()
+
def is_active(self):
return self.mode != INACTIVE
+
def get_axis_position(self, position):
return position[self.axis] * self.scale + self.offset
+
def apply_transform(self):
ffi_main, ffi_lib = chelper.get_ffi()
for sk in self.sks:
ffi_lib.dual_carriage_set_transform(
- sk, self.ENC_AXES[self.axis], self.scale, self.offset)
- self.printer.send_event('dual_carriage:update_kinematics')
+ sk, self.ENC_AXES[self.axis], self.scale, self.offset
+ )
+ self.printer.send_event("dual_carriage:update_kinematics")
+
def activate(self, mode, position, old_position=None):
old_axis_position = self.get_axis_position(old_position or position)
- self.scale = -1. if mode == MIRROR else 1.
+ self.scale = -1.0 if mode == MIRROR else 1.0
self.offset = old_axis_position - position[self.axis] * self.scale
self.apply_transform()
self.mode = mode
+
def inactivate(self, position):
self.offset = self.get_axis_position(position)
- self.scale = 0.
+ self.scale = 0.0
self.apply_transform()
self.mode = INACTIVE
+
def override_axis_scaling(self, new_scale, position):
old_axis_position = self.get_axis_position(position)
self.scale = math.copysign(new_scale, self.scale)
diff --git a/klippy/kinematics/kinematic_stepper.py b/klippy/kinematics/kinematic_stepper.py
index c82f0855..7d983ed1 100644
--- a/klippy/kinematics/kinematic_stepper.py
+++ b/klippy/kinematics/kinematic_stepper.py
@@ -7,86 +7,105 @@
import logging, re
import stepper, chelper
+
def parse_carriages_string(carriages_str, printer_carriages, parse_error):
nxt = 0
- pat = re.compile('[+-]')
- coeffs = [0.] * 3
+ pat = re.compile("[+-]")
+ coeffs = [0.0] * 3
ref_carriages = []
while nxt < len(carriages_str):
- match = pat.search(carriages_str, nxt+1)
+ match = pat.search(carriages_str, nxt + 1)
end = len(carriages_str) if match is None else match.start()
term = carriages_str[nxt:end].strip()
- term_lst = term.split('*')
+ term_lst = term.split("*")
if len(term_lst) not in [1, 2]:
- raise parse_error(
- "Invalid term '%s' in '%s'" % (term, carriages_str))
+ raise parse_error("Invalid term '%s' in '%s'" % (term, carriages_str))
if len(term_lst) == 2:
try:
coeff = float(term_lst[0])
except ValueError:
raise error("Invalid float '%s'" % term_lst[0])
else:
- coeff = -1. if term_lst[0].startswith('-') else 1.
- if term_lst[0].startswith('-') or term_lst[0].startswith('+'):
+ coeff = -1.0 if term_lst[0].startswith("-") else 1.0
+ if term_lst[0].startswith("-") or term_lst[0].startswith("+"):
term_lst[0] = term_lst[0][1:]
c = term_lst[-1]
if c not in printer_carriages:
- raise parse_error("Invalid '%s' carriage referenced in '%s'" %
- (c, carriages_str))
+ raise parse_error(
+ "Invalid '%s' carriage referenced in '%s'" % (c, carriages_str)
+ )
carriage = printer_carriages[c]
j = carriage.get_axis()
if coeffs[j]:
- raise error("Carriage '%s' was referenced multiple times in '%s'" %
- (c, carriages_str))
+ raise error(
+ "Carriage '%s' was referenced multiple times in '%s'"
+ % (c, carriages_str)
+ )
coeffs[j] = coeff
ref_carriages.append(carriage)
nxt = end
return coeffs, ref_carriages
+
class KinematicStepper:
def __init__(self, config, printer_carriages):
self.printer = config.get_printer()
self.stepper = stepper.PrinterStepper(config)
self.kin_coeffs, self.carriages = parse_carriages_string(
- config.get('carriages'), printer_carriages, config.error)
+ config.get("carriages"), printer_carriages, config.error
+ )
if not any(self.kin_coeffs):
raise config.error(
- "'%s' must provide a valid 'carriages' configuration" %
- self.stepper.get_name())
+ "'%s' must provide a valid 'carriages' configuration"
+ % self.stepper.get_name()
+ )
self.stepper.setup_itersolve(
- 'generic_cartesian_stepper_alloc',
- self.kin_coeffs[0], self.kin_coeffs[1], self.kin_coeffs[2])
+ "generic_cartesian_stepper_alloc",
+ self.kin_coeffs[0],
+ self.kin_coeffs[1],
+ self.kin_coeffs[2],
+ )
self.stepper_sk = self.stepper.get_stepper_kinematics()
# Add stepper to the carriages it references
for sc in self.carriages:
sc.add_stepper(self)
+
def get_name(self, short=False):
name = self.stepper.get_name(short)
- if short and name.startswith('stepper '):
+ if short and name.startswith("stepper "):
return name[8:]
return name
+
def get_stepper(self):
return self.stepper
+
def get_kin_coeffs(self):
return tuple(self.kin_coeffs)
+
def get_active_axes(self):
return [i for i, c in enumerate(self.kin_coeffs) if c]
+
def get_carriages(self):
return self.carriages
+
def update_kin_coeffs(self, kin_coeffs):
self.kin_coeffs = kin_coeffs
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.generic_cartesian_stepper_set_coeffs(
- self.stepper_sk, kin_coeffs[0], kin_coeffs[1], kin_coeffs[2])
+ self.stepper_sk, kin_coeffs[0], kin_coeffs[1], kin_coeffs[2]
+ )
+
def update_carriages(self, carriages_str, printer_carriages, report_error):
kin_coeffs, carriages = parse_carriages_string(
- carriages_str, printer_carriages,
- report_error or self.printer.command_error)
+ carriages_str, printer_carriages, report_error or self.printer.command_error
+ )
if report_error is not None and not any(kin_coeffs):
raise report_error(
- "A valid string that references at least one carriage"
- " must be provided for '%s'" % self.get_name())
+ "A valid string that references at least one carriage"
+ " must be provided for '%s'" % self.get_name()
+ )
self.carriages = carriages
self.update_kin_coeffs(kin_coeffs)
+
def set_position(self, coord):
self.stepper.set_position(coord)
diff --git a/klippy/kinematics/none.py b/klippy/kinematics/none.py
index d9f9d21d..3c37217c 100644
--- a/klippy/kinematics/none.py
+++ b/klippy/kinematics/none.py
@@ -4,27 +4,36 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+
class NoneKinematics:
def __init__(self, toolhead, config):
- self.axes_minmax = toolhead.Coord(0., 0., 0., 0.)
+ self.axes_minmax = toolhead.Coord(0.0, 0.0, 0.0, 0.0)
+
def get_steppers(self):
return []
+
def calc_position(self, stepper_positions):
return [0, 0, 0]
+
def set_position(self, newpos, homing_axes):
pass
+
def clear_homing_state(self, clear_axes):
pass
+
def home(self, homing_state):
pass
+
def check_move(self, move):
pass
+
def get_status(self, eventtime):
return {
- 'homed_axes': '',
- 'axis_minimum': self.axes_minmax,
- 'axis_maximum': self.axes_minmax,
+ "homed_axes": "",
+ "axis_minimum": self.axes_minmax,
+ "axis_maximum": self.axes_minmax,
}
+
def load_kinematics(toolhead, config):
return NoneKinematics(toolhead, config)
diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py
index ffa15d83..cd480a42 100644
--- a/klippy/kinematics/polar.py
+++ b/klippy/kinematics/polar.py
@@ -6,55 +6,64 @@
import logging, math
import stepper
+
class PolarKinematics:
def __init__(self, toolhead, config):
# Setup axis steppers
- stepper_bed = stepper.PrinterStepper(config.getsection('stepper_bed'),
- units_in_radians=True)
- rail_arm = stepper.LookupRail(config.getsection('stepper_arm'))
- rail_z = stepper.LookupMultiRail(config.getsection('stepper_z'))
- stepper_bed.setup_itersolve('polar_stepper_alloc', b'a')
- rail_arm.setup_itersolve('polar_stepper_alloc', b'r')
- rail_z.setup_itersolve('cartesian_stepper_alloc', b'z')
+ stepper_bed = stepper.PrinterStepper(
+ config.getsection("stepper_bed"), units_in_radians=True
+ )
+ rail_arm = stepper.LookupRail(config.getsection("stepper_arm"))
+ rail_z = stepper.LookupMultiRail(config.getsection("stepper_z"))
+ stepper_bed.setup_itersolve("polar_stepper_alloc", b"a")
+ rail_arm.setup_itersolve("polar_stepper_alloc", b"r")
+ rail_z.setup_itersolve("cartesian_stepper_alloc", b"z")
self.rails = [rail_arm, rail_z]
- self.steppers = [stepper_bed] + [ s for r in self.rails
- for s in r.get_steppers() ]
+ self.steppers = [stepper_bed] + [
+ s for r in self.rails for s in r.get_steppers()
+ ]
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat(
- 'max_z_velocity', max_velocity, above=0., maxval=max_velocity)
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
self.max_z_accel = config.getfloat(
- 'max_z_accel', max_accel, above=0., maxval=max_accel)
+ "max_z_accel", max_accel, above=0.0, maxval=max_accel
+ )
self.limit_z = (1.0, -1.0)
- self.limit_xy2 = -1.
+ self.limit_xy2 = -1.0
max_xy = self.rails[0].get_range()[1]
min_z, max_z = self.rails[1].get_range()
- self.axes_min = toolhead.Coord(-max_xy, -max_xy, min_z, 0.)
- self.axes_max = toolhead.Coord(max_xy, max_xy, max_z, 0.)
+ self.axes_min = toolhead.Coord(-max_xy, -max_xy, min_z, 0.0)
+ self.axes_max = toolhead.Coord(max_xy, max_xy, max_z, 0.0)
+
def get_steppers(self):
return list(self.steppers)
+
def calc_position(self, stepper_positions):
bed_angle = stepper_positions[self.steppers[0].get_name()]
arm_pos = stepper_positions[self.rails[0].get_name()]
z_pos = stepper_positions[self.rails[1].get_name()]
- return [math.cos(bed_angle) * arm_pos, math.sin(bed_angle) * arm_pos,
- z_pos]
+ return [math.cos(bed_angle) * arm_pos, math.sin(bed_angle) * arm_pos, z_pos]
+
def set_position(self, newpos, homing_axes):
for s in self.steppers:
s.set_position(newpos)
if "z" in homing_axes:
self.limit_z = self.rails[1].get_range()
if "x" in homing_axes and "y" in homing_axes:
- self.limit_xy2 = self.rails[0].get_range()[1]**2
+ self.limit_xy2 = self.rails[0].get_range()[1] ** 2
+
def clear_homing_state(self, clear_axes):
if "x" in clear_axes or "y" in clear_axes:
# X and Y cannot be cleared separately
- self.limit_xy2 = -1.
+ self.limit_xy2 = -1.0
if "z" in clear_axes:
self.limit_z = (1.0, -1.0)
+
def _home_axis(self, homing_state, axis, rail):
# Determine movement
position_min, position_max = rail.get_range()
@@ -62,7 +71,7 @@ class PolarKinematics:
homepos = [None, None, None, None]
homepos[axis] = hi.position_endstop
if axis == 0:
- homepos[1] = 0.
+ homepos[1] = 0.0
forcepos = list(homepos)
if hi.positive_dir:
forcepos[axis] -= hi.position_endstop - position_min
@@ -70,6 +79,7 @@ class PolarKinematics:
forcepos[axis] += position_max - hi.position_endstop
# Perform homing
homing_state.home_rails([rail], forcepos, homepos)
+
def home(self, homing_state):
# Always home XY together
homing_axes = homing_state.get_axes()
@@ -86,11 +96,12 @@ class PolarKinematics:
self._home_axis(homing_state, 0, self.rails[0])
if home_z:
self._home_axis(homing_state, 2, self.rails[1])
+
def check_move(self, move):
end_pos = move.end_pos
- xy2 = end_pos[0]**2 + end_pos[1]**2
+ xy2 = end_pos[0] ** 2 + end_pos[1] ** 2
if xy2 > self.limit_xy2:
- if self.limit_xy2 < 0.:
+ if self.limit_xy2 < 0.0:
raise move.move_error("Must home axis first")
raise move.move_error()
if move.axes_d[2]:
@@ -100,16 +111,17 @@ class PolarKinematics:
raise move.move_error()
# Move with Z - update velocity and accel for slower Z axis
z_ratio = move.move_d / abs(move.axes_d[2])
- move.limit_speed(self.max_z_velocity * z_ratio,
- self.max_z_accel * z_ratio)
+ move.limit_speed(self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
+
def get_status(self, eventtime):
- xy_home = "xy" if self.limit_xy2 >= 0. else ""
+ xy_home = "xy" if self.limit_xy2 >= 0.0 else ""
z_home = "z" if self.limit_z[0] <= self.limit_z[1] else ""
return {
- 'homed_axes': xy_home + z_home,
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
+ "homed_axes": xy_home + z_home,
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return PolarKinematics(toolhead, config)
diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py
index 732a89a8..7bc3a4d8 100644
--- a/klippy/kinematics/rotary_delta.py
+++ b/klippy/kinematics/rotary_delta.py
@@ -6,102 +6,130 @@
import math, logging
import stepper, mathutil, chelper
+
class RotaryDeltaKinematics:
def __init__(self, toolhead, config):
# Setup tower rails
- stepper_configs = [config.getsection('stepper_' + a) for a in 'abc']
+ stepper_configs = [config.getsection("stepper_" + a) for a in "abc"]
rail_a = stepper.LookupRail(
- stepper_configs[0], need_position_minmax=False,
- units_in_radians=True)
+ stepper_configs[0], need_position_minmax=False, units_in_radians=True
+ )
a_endstop = rail_a.get_homing_info().position_endstop
rail_b = stepper.LookupRail(
- stepper_configs[1], need_position_minmax=False,
- default_position_endstop=a_endstop, units_in_radians=True)
+ stepper_configs[1],
+ need_position_minmax=False,
+ default_position_endstop=a_endstop,
+ units_in_radians=True,
+ )
rail_c = stepper.LookupRail(
- stepper_configs[2], need_position_minmax=False,
- default_position_endstop=a_endstop, units_in_radians=True)
+ stepper_configs[2],
+ need_position_minmax=False,
+ default_position_endstop=a_endstop,
+ units_in_radians=True,
+ )
self.rails = [rail_a, rail_b, rail_c]
# Read config
max_velocity, max_accel = toolhead.get_max_velocity()
- self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
- above=0., maxval=max_velocity)
- shoulder_radius = config.getfloat('shoulder_radius', above=0.)
- shoulder_height = config.getfloat('shoulder_height', above=0.)
- a_upper_arm = stepper_configs[0].getfloat('upper_arm_length', above=0.)
+ self.max_z_velocity = config.getfloat(
+ "max_z_velocity", max_velocity, above=0.0, maxval=max_velocity
+ )
+ shoulder_radius = config.getfloat("shoulder_radius", above=0.0)
+ shoulder_height = config.getfloat("shoulder_height", above=0.0)
+ a_upper_arm = stepper_configs[0].getfloat("upper_arm_length", above=0.0)
upper_arms = [
- sconfig.getfloat('upper_arm_length', a_upper_arm, above=0.)
- for sconfig in stepper_configs]
- a_lower_arm = stepper_configs[0].getfloat('lower_arm_length', above=0.)
+ sconfig.getfloat("upper_arm_length", a_upper_arm, above=0.0)
+ for sconfig in stepper_configs
+ ]
+ a_lower_arm = stepper_configs[0].getfloat("lower_arm_length", above=0.0)
lower_arms = [
- sconfig.getfloat('lower_arm_length', a_lower_arm, above=0.)
- for sconfig in stepper_configs]
- angles = [sconfig.getfloat('angle', angle)
- for sconfig, angle in zip(stepper_configs, [30., 150., 270.])]
+ sconfig.getfloat("lower_arm_length", a_lower_arm, above=0.0)
+ for sconfig in stepper_configs
+ ]
+ angles = [
+ sconfig.getfloat("angle", angle)
+ for sconfig, angle in zip(stepper_configs, [30.0, 150.0, 270.0])
+ ]
# Setup rotary delta calibration helper
- endstops = [rail.get_homing_info().position_endstop
- for rail in self.rails]
- stepdists = [rail.get_steppers()[0].get_step_dist()
- for rail in self.rails]
+ endstops = [rail.get_homing_info().position_endstop for rail in self.rails]
+ stepdists = [rail.get_steppers()[0].get_step_dist() for rail in self.rails]
self.calibration = RotaryDeltaCalibration(
- shoulder_radius, shoulder_height, angles, upper_arms, lower_arms,
- endstops, stepdists)
+ shoulder_radius,
+ shoulder_height,
+ angles,
+ upper_arms,
+ lower_arms,
+ endstops,
+ stepdists,
+ )
# Setup iterative solver
for r, a, ua, la in zip(self.rails, angles, upper_arms, lower_arms):
- r.setup_itersolve('rotary_delta_stepper_alloc',
- shoulder_radius, shoulder_height,
- math.radians(a), ua, la)
+ r.setup_itersolve(
+ "rotary_delta_stepper_alloc",
+ shoulder_radius,
+ shoulder_height,
+ math.radians(a),
+ ua,
+ la,
+ )
for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
self.need_home = True
- self.limit_xy2 = -1.
- eangles = [r.calc_position_from_coord([0., 0., ep])
- for r, ep in zip(self.rails, endstops)]
- self.home_position = tuple(
- self.calibration.actuator_to_cartesian(eangles))
+ self.limit_xy2 = -1.0
+ eangles = [
+ r.calc_position_from_coord([0.0, 0.0, ep])
+ for r, ep in zip(self.rails, endstops)
+ ]
+ self.home_position = tuple(self.calibration.actuator_to_cartesian(eangles))
self.max_z = min(endstops)
- self.min_z = config.getfloat('minimum_z_position', 0, maxval=self.max_z)
+ self.min_z = config.getfloat("minimum_z_position", 0, maxval=self.max_z)
min_ua = min([shoulder_radius + ua for ua in upper_arms])
min_la = min([la - shoulder_radius for la in lower_arms])
- self.max_xy2 = min(min_ua, min_la)**2
- arm_z = [self.calibration.elbow_coord(i, ea)[2]
- for i, ea in enumerate(eangles)]
+ self.max_xy2 = min(min_ua, min_la) ** 2
+ arm_z = [self.calibration.elbow_coord(i, ea)[2] for i, ea in enumerate(eangles)]
self.limit_z = min([az - la for az, la in zip(arm_z, lower_arms)])
logging.info(
"Delta max build height %.2fmm (radius tapered above %.2fmm)"
- % (self.max_z, self.limit_z))
+ % (self.max_z, self.limit_z)
+ )
max_xy = math.sqrt(self.max_xy2)
- self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.)
- self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.)
- self.set_position([0., 0., 0.], "")
+ self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.0)
+ self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.0)
+ self.set_position([0.0, 0.0, 0.0], "")
+
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
+
def calc_position(self, stepper_positions):
spos = [stepper_positions[rail.get_name()] for rail in self.rails]
return self.calibration.actuator_to_cartesian(spos)
+
def set_position(self, newpos, homing_axes):
for rail in self.rails:
rail.set_position(newpos)
- self.limit_xy2 = -1.
+ self.limit_xy2 = -1.0
if homing_axes == "xyz":
self.need_home = False
+
def clear_homing_state(self, clear_axes):
# Clearing homing state for each axis individually is not implemented
if clear_axes:
self.limit_xy2 = -1
self.need_home = True
+
def home(self, homing_state):
# All axes are homed simultaneously
homing_state.set_axes([0, 1, 2])
forcepos = list(self.home_position)
- #min_angles = [-.5 * math.pi] * 3
- #forcepos[2] = self.calibration.actuator_to_cartesian(min_angles)[2]
- forcepos[2] = -1.
+ # min_angles = [-.5 * math.pi] * 3
+ # forcepos[2] = self.calibration.actuator_to_cartesian(min_angles)[2]
+ forcepos[2] = -1.0
homing_state.home_rails(self.rails, forcepos, self.home_position)
+
def check_move(self, move):
end_pos = move.end_pos
- end_xy2 = end_pos[0]**2 + end_pos[1]**2
+ end_xy2 = end_pos[0] ** 2 + end_pos[1] ** 2
if end_xy2 <= self.limit_xy2 and not move.axes_d[2]:
# Normal XY move
return
@@ -110,30 +138,44 @@ class RotaryDeltaKinematics:
end_z = end_pos[2]
limit_xy2 = self.max_xy2
if end_z > self.limit_z:
- limit_xy2 = min(limit_xy2, (self.max_z - end_z)**2)
+ limit_xy2 = min(limit_xy2, (self.max_z - end_z) ** 2)
if end_xy2 > limit_xy2 or end_z > self.max_z or end_z < self.min_z:
# Move out of range - verify not a homing move
- if (end_pos[:2] != self.home_position[:2]
- or end_z < self.min_z or end_z > self.home_position[2]):
+ if (
+ end_pos[:2] != self.home_position[:2]
+ or end_z < self.min_z
+ or end_z > self.home_position[2]
+ ):
raise move.move_error()
- limit_xy2 = -1.
+ limit_xy2 = -1.0
if move.axes_d[2]:
move.limit_speed(self.max_z_velocity, move.accel)
- limit_xy2 = -1.
+ limit_xy2 = -1.0
self.limit_xy2 = limit_xy2
+
def get_status(self, eventtime):
return {
- 'homed_axes': '' if self.need_home else 'xyz',
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
+ "homed_axes": "" if self.need_home else "xyz",
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def get_calibration(self):
return self.calibration
+
# Rotary delta parameter calibration for DELTA_CALIBRATE tool
class RotaryDeltaCalibration:
- def __init__(self, shoulder_radius, shoulder_height, angles,
- upper_arms, lower_arms, endstops, stepdists):
+ def __init__(
+ self,
+ shoulder_radius,
+ shoulder_height,
+ angles,
+ upper_arms,
+ lower_arms,
+ endstops,
+ stepdists,
+ ):
self.shoulder_radius = shoulder_radius
self.shoulder_height = shoulder_height
self.angles = angles
@@ -143,39 +185,56 @@ class RotaryDeltaCalibration:
self.stepdists = stepdists
# Calculate the absolute angle of each endstop
ffi_main, self.ffi_lib = chelper.get_ffi()
- self.sks = [ffi_main.gc(self.ffi_lib.rotary_delta_stepper_alloc(
- shoulder_radius, shoulder_height, math.radians(a), ua, la),
- self.ffi_lib.free)
- for a, ua, la in zip(angles, upper_arms, lower_arms)]
+ self.sks = [
+ ffi_main.gc(
+ self.ffi_lib.rotary_delta_stepper_alloc(
+ shoulder_radius, shoulder_height, math.radians(a), ua, la
+ ),
+ self.ffi_lib.free,
+ )
+ for a, ua, la in zip(angles, upper_arms, lower_arms)
+ ]
self.abs_endstops = [
- self.ffi_lib.itersolve_calc_position_from_coord(sk, 0., 0., es)
- for sk, es in zip(self.sks, endstops)]
+ self.ffi_lib.itersolve_calc_position_from_coord(sk, 0.0, 0.0, es)
+ for sk, es in zip(self.sks, endstops)
+ ]
+
def coordinate_descent_params(self, is_extended):
# Determine adjustment parameters (for use with coordinate_descent)
- adj_params = ('shoulder_height', 'endstop_a', 'endstop_b', 'endstop_c')
+ adj_params = ("shoulder_height", "endstop_a", "endstop_b", "endstop_c")
if is_extended:
- adj_params += ('shoulder_radius', 'angle_a', 'angle_b')
- params = { 'shoulder_radius': self.shoulder_radius,
- 'shoulder_height': self.shoulder_height }
- for i, axis in enumerate('abc'):
- params['angle_'+axis] = self.angles[i]
- params['upper_arm_'+axis] = self.upper_arms[i]
- params['lower_arm_'+axis] = self.lower_arms[i]
- params['endstop_'+axis] = self.endstops[i]
- params['stepdist_'+axis] = self.stepdists[i]
+ adj_params += ("shoulder_radius", "angle_a", "angle_b")
+ params = {
+ "shoulder_radius": self.shoulder_radius,
+ "shoulder_height": self.shoulder_height,
+ }
+ for i, axis in enumerate("abc"):
+ params["angle_" + axis] = self.angles[i]
+ params["upper_arm_" + axis] = self.upper_arms[i]
+ params["lower_arm_" + axis] = self.lower_arms[i]
+ params["endstop_" + axis] = self.endstops[i]
+ params["stepdist_" + axis] = self.stepdists[i]
return adj_params, params
+
def new_calibration(self, params):
# Create a new calibration object from coordinate_descent params
- shoulder_radius = params['shoulder_radius']
- shoulder_height = params['shoulder_height']
- angles = [params['angle_'+a] for a in 'abc']
- upper_arms = [params['upper_arm_'+a] for a in 'abc']
- lower_arms = [params['lower_arm_'+a] for a in 'abc']
- endstops = [params['endstop_'+a] for a in 'abc']
- stepdists = [params['stepdist_'+a] for a in 'abc']
+ shoulder_radius = params["shoulder_radius"]
+ shoulder_height = params["shoulder_height"]
+ angles = [params["angle_" + a] for a in "abc"]
+ upper_arms = [params["upper_arm_" + a] for a in "abc"]
+ lower_arms = [params["lower_arm_" + a] for a in "abc"]
+ endstops = [params["endstop_" + a] for a in "abc"]
+ stepdists = [params["stepdist_" + a] for a in "abc"]
return RotaryDeltaCalibration(
- shoulder_radius, shoulder_height, angles, upper_arms, lower_arms,
- endstops, stepdists)
+ shoulder_radius,
+ shoulder_height,
+ angles,
+ upper_arms,
+ lower_arms,
+ endstops,
+ stepdists,
+ )
+
def elbow_coord(self, elbow_id, spos):
# Calculate elbow position in coordinate system at shoulder joint
sj_elbow_x = self.upper_arms[elbow_id] * math.cos(spos)
@@ -186,43 +245,59 @@ class RotaryDeltaCalibration:
y = (sj_elbow_x + self.shoulder_radius) * math.sin(angle)
z = sj_elbow_y + self.shoulder_height
return (x, y, z)
+
def actuator_to_cartesian(self, spos):
sphere_coords = [self.elbow_coord(i, sp) for i, sp in enumerate(spos)]
lower_arm2 = [la**2 for la in self.lower_arms]
return mathutil.trilateration(sphere_coords, lower_arm2)
+
def get_position_from_stable(self, stable_position):
# Return cartesian coordinates for the given stable_position
- spos = [ea - sp * sd
- for ea, sp, sd in zip(self.abs_endstops, stable_position,
- self.stepdists)]
+ spos = [
+ ea - sp * sd
+ for ea, sp, sd in zip(self.abs_endstops, stable_position, self.stepdists)
+ ]
return self.actuator_to_cartesian(spos)
+
def calc_stable_position(self, coord):
# Return a stable_position from a cartesian coordinate
- pos = [ self.ffi_lib.itersolve_calc_position_from_coord(
- sk, coord[0], coord[1], coord[2])
- for sk in self.sks ]
- return [(ep - sp) / sd
- for sd, ep, sp in zip(self.stepdists, self.abs_endstops, pos)]
+ pos = [
+ self.ffi_lib.itersolve_calc_position_from_coord(
+ sk, coord[0], coord[1], coord[2]
+ )
+ for sk in self.sks
+ ]
+ return [
+ (ep - sp) / sd for sd, ep, sp in zip(self.stepdists, self.abs_endstops, pos)
+ ]
+
def save_state(self, configfile):
# Save the current parameters (for use with SAVE_CONFIG)
- configfile.set('printer', 'shoulder_radius', "%.6f"
- % (self.shoulder_radius,))
- configfile.set('printer', 'shoulder_height', "%.6f"
- % (self.shoulder_height,))
- for i, axis in enumerate('abc'):
- configfile.set('stepper_'+axis, 'angle', "%.6f" % (self.angles[i],))
- configfile.set('stepper_'+axis, 'position_endstop',
- "%.6f" % (self.endstops[i],))
+ configfile.set("printer", "shoulder_radius", "%.6f" % (self.shoulder_radius,))
+ configfile.set("printer", "shoulder_height", "%.6f" % (self.shoulder_height,))
+ for i, axis in enumerate("abc"):
+ configfile.set("stepper_" + axis, "angle", "%.6f" % (self.angles[i],))
+ configfile.set(
+ "stepper_" + axis, "position_endstop", "%.6f" % (self.endstops[i],)
+ )
gcode = configfile.get_printer().lookup_object("gcode")
gcode.respond_info(
"stepper_a: position_endstop: %.6f angle: %.6f\n"
"stepper_b: position_endstop: %.6f angle: %.6f\n"
"stepper_c: position_endstop: %.6f angle: %.6f\n"
"shoulder_radius: %.6f shoulder_height: %.6f"
- % (self.endstops[0], self.angles[0],
- self.endstops[1], self.angles[1],
- self.endstops[2], self.angles[2],
- self.shoulder_radius, self.shoulder_height))
+ % (
+ self.endstops[0],
+ self.angles[0],
+ self.endstops[1],
+ self.angles[1],
+ self.endstops[2],
+ self.angles[2],
+ self.shoulder_radius,
+ self.shoulder_height,
+ )
+ )
+
def load_kinematics(toolhead, config):
return RotaryDeltaKinematics(toolhead, config)
diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py
index 47bc6855..290a7f27 100644
--- a/klippy/kinematics/winch.py
+++ b/klippy/kinematics/winch.py
@@ -5,54 +5,63 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import stepper, mathutil
+
class WinchKinematics:
def __init__(self, toolhead, config):
# Setup steppers at each anchor
self.steppers = []
self.anchors = []
for i in range(26):
- name = 'stepper_' + chr(ord('a') + i)
+ name = "stepper_" + chr(ord("a") + i)
if i >= 3 and not config.has_section(name):
break
stepper_config = config.getsection(name)
s = stepper.PrinterStepper(stepper_config)
self.steppers.append(s)
- a = tuple([stepper_config.getfloat('anchor_' + n) for n in 'xyz'])
+ a = tuple([stepper_config.getfloat("anchor_" + n) for n in "xyz"])
self.anchors.append(a)
- s.setup_itersolve('winch_stepper_alloc', *a)
+ s.setup_itersolve("winch_stepper_alloc", *a)
s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks
acoords = list(zip(*self.anchors))
- self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.)
- self.axes_max = toolhead.Coord(*[max(a) for a in acoords], e=0.)
- self.set_position([0., 0., 0.], "")
+ self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.0)
+ self.axes_max = toolhead.Coord(*[max(a) for a in acoords], e=0.0)
+ self.set_position([0.0, 0.0, 0.0], "")
+
def get_steppers(self):
return list(self.steppers)
+
def calc_position(self, stepper_positions):
# Use only first three steppers to calculate cartesian position
pos = [stepper_positions[rail.get_name()] for rail in self.steppers[:3]]
- return mathutil.trilateration(self.anchors[:3], [sp*sp for sp in pos])
+ return mathutil.trilateration(self.anchors[:3], [sp * sp for sp in pos])
+
def set_position(self, newpos, homing_axes):
for s in self.steppers:
s.set_position(newpos)
+
def clear_homing_state(self, clear_axes):
# XXX - homing not implemented
pass
+
def home(self, homing_state):
# XXX - homing not implemented
homing_state.set_axes([0, 1, 2])
- homing_state.set_homed_position([0., 0., 0.])
+ homing_state.set_homed_position([0.0, 0.0, 0.0])
+
def check_move(self, move):
# XXX - boundary checks and speed limits not implemented
pass
+
def get_status(self, eventtime):
# XXX - homed_checks and rail limits not implemented
return {
- 'homed_axes': 'xyz',
- 'axis_minimum': self.axes_min,
- 'axis_maximum': self.axes_max,
+ "homed_axes": "xyz",
+ "axis_minimum": self.axes_min,
+ "axis_maximum": self.axes_max,
}
+
def load_kinematics(toolhead, config):
return WinchKinematics(toolhead, config)
diff --git a/klippy/klippy.py b/klippy/klippy.py
index 316343cb..757a2efc 100644
--- a/klippy/klippy.py
+++ b/klippy/klippy.py
@@ -22,9 +22,11 @@ command to reload the config and restart the host software.
Printer is halted
"""
+
class Printer:
config_error = configfile.error
command_error = gcode.CommandError
+
def __init__(self, main_reactor, bglogger, start_args):
self.bglogger = bglogger
self.start_args = start_args
@@ -38,10 +40,13 @@ class Printer:
# Init printer components that must be setup prior to config
for m in [gcode, webhooks]:
m.add_early_printer_objects(self)
+
def get_start_args(self):
return self.start_args
+
def get_reactor(self):
return self.reactor
+
def get_state_message(self):
if self.state_message == message_ready:
category = "ready"
@@ -52,58 +57,64 @@ class Printer:
else:
category = "error"
return self.state_message, category
+
def is_shutdown(self):
return self.in_shutdown_state
+
def _set_state(self, msg):
if self.state_message in (message_ready, message_startup):
self.state_message = msg
- if (msg != message_ready
- and self.start_args.get('debuginput') is not None):
- self.request_exit('error_exit')
+ if msg != message_ready and self.start_args.get("debuginput") is not None:
+ self.request_exit("error_exit")
+
def update_error_msg(self, oldmsg, newmsg):
- if (self.state_message != oldmsg
+ if (
+ self.state_message != oldmsg
or self.state_message in (message_ready, message_startup)
- or newmsg in (message_ready, message_startup)):
+ or newmsg in (message_ready, message_startup)
+ ):
return
self.state_message = newmsg
logging.error(newmsg)
+
def add_object(self, name, obj):
if name in self.objects:
- raise self.config_error(
- "Printer object '%s' already created" % (name,))
+ raise self.config_error("Printer object '%s' already created" % (name,))
self.objects[name] = obj
+
def lookup_object(self, name, default=configfile.sentinel):
if name in self.objects:
return self.objects[name]
if default is configfile.sentinel:
raise self.config_error("Unknown config object '%s'" % (name,))
return default
+
def lookup_objects(self, module=None):
if module is None:
return list(self.objects.items())
- prefix = module + ' '
- objs = [(n, self.objects[n])
- for n in self.objects if n.startswith(prefix)]
+ prefix = module + " "
+ objs = [(n, self.objects[n]) for n in self.objects if n.startswith(prefix)]
if module in self.objects:
return [(module, self.objects[module])] + objs
return objs
+
def load_object(self, config, section, default=configfile.sentinel):
if section in self.objects:
return self.objects[section]
module_parts = section.split()
module_name = module_parts[0]
- py_name = os.path.join(os.path.dirname(__file__),
- 'extras', module_name + '.py')
- py_dirname = os.path.join(os.path.dirname(__file__),
- 'extras', module_name, '__init__.py')
+ py_name = os.path.join(os.path.dirname(__file__), "extras", module_name + ".py")
+ py_dirname = os.path.join(
+ os.path.dirname(__file__), "extras", module_name, "__init__.py"
+ )
if not os.path.exists(py_name) and not os.path.exists(py_dirname):
if default is not configfile.sentinel:
return default
raise self.config_error("Unable to load module '%s'" % (section,))
- mod = importlib.import_module('extras.' + module_name)
- init_func = 'load_config'
+ mod = importlib.import_module("extras." + module_name)
+ init_func = "load_config"
if len(module_parts) > 1:
- init_func = 'load_config_prefix'
+ init_func = "load_config_prefix"
init_func = getattr(mod, init_func, None)
if init_func is None:
if default is not configfile.sentinel:
@@ -111,20 +122,22 @@ class Printer:
raise self.config_error("Unable to load module '%s'" % (section,))
self.objects[section] = init_func(config.getsection(section))
return self.objects[section]
+
def _read_config(self):
- self.objects['configfile'] = pconfig = configfile.PrinterConfig(self)
+ self.objects["configfile"] = pconfig = configfile.PrinterConfig(self)
config = pconfig.read_main_config()
if self.bglogger is not None:
pconfig.log_config(config)
# Create printer components
for m in [pins, mcu]:
m.add_printer_objects(config)
- for section_config in config.get_prefix_sections(''):
+ for section_config in config.get_prefix_sections(""):
self.load_object(config, section_config.get_name(), None)
for m in [toolhead]:
m.add_printer_objects(config)
# Validate that there are no undefined parameters in the config file
pconfig.check_unused_options(config)
+
def _connect(self, eventtime):
try:
self._read_config()
@@ -153,8 +166,13 @@ class Printer:
return
except Exception as e:
logging.exception("Unhandled exception during connect")
- self._set_state("Internal error during connect: %s\n%s"
- % (str(e), message_restart,))
+ self._set_state(
+ "Internal error during connect: %s\n%s"
+ % (
+ str(e),
+ message_restart,
+ )
+ )
return
try:
self._set_state(message_ready)
@@ -164,13 +182,17 @@ class Printer:
cb()
except Exception as e:
logging.exception("Unhandled exception during ready callback")
- self.invoke_shutdown("Internal error during ready callback: %s"
- % (str(e),))
+ self.invoke_shutdown("Internal error during ready callback: %s" % (str(e),))
+
def run(self):
systime = time.time()
monotime = self.reactor.monotonic()
- logging.info("Start printer at %s (%.1f %.1f)",
- time.asctime(time.localtime(systime)), systime, monotime)
+ logging.info(
+ "Start printer at %s (%.1f %.1f)",
+ time.asctime(time.localtime(systime)),
+ systime,
+ monotime,
+ )
# Enter main reactor loop
try:
self.reactor.run()
@@ -179,8 +201,7 @@ class Printer:
logging.exception(msg)
# Exception from a reactor callback - try to shutdown
try:
- self.reactor.register_callback((lambda e:
- self.invoke_shutdown(msg)))
+ self.reactor.register_callback((lambda e: self.invoke_shutdown(msg)))
self.reactor.run()
except:
logging.exception("Repeat unhandled exception during run")
@@ -189,17 +210,19 @@ class Printer:
# Check restart flags
run_result = self.run_result
try:
- if run_result == 'firmware_restart':
+ if run_result == "firmware_restart":
self.send_event("klippy:firmware_restart")
self.send_event("klippy:disconnect")
except:
logging.exception("Unhandled exception during post run")
return run_result
+
def set_rollover_info(self, name, info, log=True):
if log:
logging.info(info)
if self.bglogger is not None:
self.bglogger.set_rollover_info(name, info)
+
def invoke_shutdown(self, msg, details={}):
if self.in_shutdown_state:
return
@@ -211,16 +234,20 @@ class Printer:
cb()
except:
logging.exception("Exception during shutdown handler")
- logging.info("Reactor garbage collection: %s",
- self.reactor.get_gc_stats())
+ logging.info("Reactor garbage collection: %s", self.reactor.get_gc_stats())
self.send_event("klippy:notify_mcu_shutdown", msg, details)
+
def invoke_async_shutdown(self, msg, details={}):
self.reactor.register_async_callback(
- (lambda e: self.invoke_shutdown(msg, details)))
+ (lambda e: self.invoke_shutdown(msg, details))
+ )
+
def register_event_handler(self, event, callback):
self.event_handlers.setdefault(event, []).append(callback)
+
def send_event(self, event, *params):
return [cb(*params) for cb in self.event_handlers.get(event, [])]
+
def request_exit(self, result):
if self.run_result is None:
self.run_result = result
@@ -231,137 +258,178 @@ class Printer:
# Startup
######################################################################
+
def import_test():
# Import all optional modules (used as a build test)
dname = os.path.dirname(__file__)
- for mname in ['extras', 'kinematics']:
+ for mname in ["extras", "kinematics"]:
for fname in os.listdir(os.path.join(dname, mname)):
- if fname.endswith('.py') and fname != '__init__.py':
+ if fname.endswith(".py") and fname != "__init__.py":
module_name = fname[:-3]
else:
- iname = os.path.join(dname, mname, fname, '__init__.py')
+ iname = os.path.join(dname, mname, fname, "__init__.py")
if not os.path.exists(iname):
continue
module_name = fname
- importlib.import_module(mname + '.' + module_name)
+ importlib.import_module(mname + "." + module_name)
sys.exit(0)
+
def arg_dictionary(option, opt_str, value, parser):
key, fname = "dictionary", value
- if '=' in value:
- mcu_name, fname = value.split('=', 1)
+ if "=" in value:
+ mcu_name, fname = value.split("=", 1)
key = "dictionary_" + mcu_name
if parser.values.dictionary is None:
parser.values.dictionary = {}
parser.values.dictionary[key] = fname
+
def main():
usage = "%prog [options] <config file>"
opts = optparse.OptionParser(usage)
- opts.add_option("-i", "--debuginput", dest="debuginput",
- help="read commands from file instead of from tty port")
- opts.add_option("-I", "--input-tty", dest="inputtty",
- default='/tmp/printer',
- help="input tty name (default is /tmp/printer)")
- opts.add_option("-a", "--api-server", dest="apiserver",
- help="api server unix domain socket filename")
- opts.add_option("-l", "--logfile", dest="logfile",
- help="write log to file instead of stderr")
- opts.add_option("-v", action="store_true", dest="verbose",
- help="enable debug messages")
- opts.add_option("-o", "--debugoutput", dest="debugoutput",
- help="write output to file instead of to serial port")
- opts.add_option("-d", "--dictionary", dest="dictionary", type="string",
- action="callback", callback=arg_dictionary,
- help="file to read for mcu protocol dictionary")
- opts.add_option("--import-test", action="store_true",
- help="perform an import module test")
+ opts.add_option(
+ "-i",
+ "--debuginput",
+ dest="debuginput",
+ help="read commands from file instead of from tty port",
+ )
+ opts.add_option(
+ "-I",
+ "--input-tty",
+ dest="inputtty",
+ default="/tmp/printer",
+ help="input tty name (default is /tmp/printer)",
+ )
+ opts.add_option(
+ "-a",
+ "--api-server",
+ dest="apiserver",
+ help="api server unix domain socket filename",
+ )
+ opts.add_option(
+ "-l", "--logfile", dest="logfile", help="write log to file instead of stderr"
+ )
+ opts.add_option(
+ "-v", action="store_true", dest="verbose", help="enable debug messages"
+ )
+ opts.add_option(
+ "-o",
+ "--debugoutput",
+ dest="debugoutput",
+ help="write output to file instead of to serial port",
+ )
+ opts.add_option(
+ "-d",
+ "--dictionary",
+ dest="dictionary",
+ type="string",
+ action="callback",
+ callback=arg_dictionary,
+ help="file to read for mcu protocol dictionary",
+ )
+ opts.add_option(
+ "--import-test", action="store_true", help="perform an import module test"
+ )
options, args = opts.parse_args()
if options.import_test:
import_test()
if len(args) != 1:
opts.error("Incorrect number of arguments")
- start_args = {'config_file': args[0], 'apiserver': options.apiserver,
- 'start_reason': 'startup'}
+ start_args = {
+ "config_file": args[0],
+ "apiserver": options.apiserver,
+ "start_reason": "startup",
+ }
debuglevel = logging.INFO
if options.verbose:
debuglevel = logging.DEBUG
if options.debuginput:
- start_args['debuginput'] = options.debuginput
- debuginput = open(options.debuginput, 'rb')
- start_args['gcode_fd'] = debuginput.fileno()
+ start_args["debuginput"] = options.debuginput
+ debuginput = open(options.debuginput, "rb")
+ start_args["gcode_fd"] = debuginput.fileno()
else:
- start_args['gcode_fd'] = util.create_pty(options.inputtty)
+ start_args["gcode_fd"] = util.create_pty(options.inputtty)
if options.debugoutput:
- start_args['debugoutput'] = options.debugoutput
+ start_args["debugoutput"] = options.debugoutput
start_args.update(options.dictionary)
bglogger = None
if options.logfile:
- start_args['log_file'] = options.logfile
+ start_args["log_file"] = options.logfile
bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel)
else:
logging.getLogger().setLevel(debuglevel)
logging.info("Starting Klippy...")
git_info = util.get_git_version()
git_vers = git_info["version"]
- extra_files = [fname for code, fname in git_info["file_status"]
- if (code in ('??', '!!') and fname.endswith('.py')
- and (fname.startswith('klippy/kinematics/')
- or fname.startswith('klippy/extras/')))]
- modified_files = [fname for code, fname in git_info["file_status"]
- if code == 'M']
+ extra_files = [
+ fname
+ for code, fname in git_info["file_status"]
+ if (
+ code in ("??", "!!")
+ and fname.endswith(".py")
+ and (
+ fname.startswith("klippy/kinematics/")
+ or fname.startswith("klippy/extras/")
+ )
+ )
+ ]
+ modified_files = [fname for code, fname in git_info["file_status"] if code == "M"]
extra_git_desc = ""
if extra_files:
- if not git_vers.endswith('-dirty'):
- git_vers = git_vers + '-dirty'
+ if not git_vers.endswith("-dirty"):
+ git_vers = git_vers + "-dirty"
if len(extra_files) > 10:
extra_files[10:] = ["(+%d files)" % (len(extra_files) - 10,)]
- extra_git_desc += "\nUntracked files: %s" % (', '.join(extra_files),)
+ extra_git_desc += "\nUntracked files: %s" % (", ".join(extra_files),)
if modified_files:
if len(modified_files) > 10:
modified_files[10:] = ["(+%d files)" % (len(modified_files) - 10,)]
- extra_git_desc += "\nModified files: %s" % (', '.join(modified_files),)
+ extra_git_desc += "\nModified files: %s" % (", ".join(modified_files),)
extra_git_desc += "\nBranch: %s" % (git_info["branch"])
extra_git_desc += "\nRemote: %s" % (git_info["remote"])
extra_git_desc += "\nTracked URL: %s" % (git_info["url"])
- start_args['software_version'] = git_vers
- start_args['cpu_info'] = util.get_cpu_info()
+ start_args["software_version"] = git_vers
+ start_args["cpu_info"] = util.get_cpu_info()
if bglogger is not None:
- versions = "\n".join([
- "Args: %s" % (sys.argv,),
- "Git version: %s%s" % (repr(start_args['software_version']),
- extra_git_desc),
- "CPU: %s" % (start_args['cpu_info'],),
- "Python: %s" % (repr(sys.version),)])
+ versions = "\n".join(
+ [
+ "Args: %s" % (sys.argv,),
+ "Git version: %s%s"
+ % (repr(start_args["software_version"]), extra_git_desc),
+ "CPU: %s" % (start_args["cpu_info"],),
+ "Python: %s" % (repr(sys.version),),
+ ]
+ )
logging.info(versions)
elif not options.debugoutput:
- logging.warning("No log file specified!"
- " Severe timing issues may result!")
+ logging.warning("No log file specified!" " Severe timing issues may result!")
gc.disable()
# Start Printer() class
while 1:
if bglogger is not None:
bglogger.clear_rollover_info()
- bglogger.set_rollover_info('versions', versions)
+ bglogger.set_rollover_info("versions", versions)
gc.collect()
main_reactor = reactor.Reactor(gc_checking=True)
printer = Printer(main_reactor, bglogger, start_args)
res = printer.run()
- if res in ['exit', 'error_exit']:
+ if res in ["exit", "error_exit"]:
break
- time.sleep(1.)
+ time.sleep(1.0)
main_reactor.finalize()
main_reactor = printer = None
logging.info("Restarting printer")
- start_args['start_reason'] = res
+ start_args["start_reason"] = res
if bglogger is not None:
bglogger.stop()
- if res == 'error_exit':
+ if res == "error_exit":
sys.exit(-1)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/klippy/mathutil.py b/klippy/mathutil.py
index c741d915..6de89c64 100644
--- a/klippy/mathutil.py
+++ b/klippy/mathutil.py
@@ -11,11 +11,12 @@ import queuelogger
# Coordinate descent
######################################################################
+
# Helper code that implements coordinate descent
def coordinate_descent(adj_params, params, error_func):
# Define potential changes
params = dict(params)
- dp = {param_name: 1. for param_name in adj_params}
+ dp = {param_name: 1.0 for param_name in adj_params}
# Calculate the error
best_err = error_func(params)
logging.info("Coordinate descent initial error: %s", best_err)
@@ -43,14 +44,15 @@ def coordinate_descent(adj_params, params, error_func):
continue
params[param_name] = orig
dp[param_name] *= 0.9
- logging.info("Coordinate descent best_err: %s rounds: %d",
- best_err, rounds)
+ logging.info("Coordinate descent best_err: %s rounds: %d", best_err, rounds)
return params
+
# Helper to run the coordinate descent function in a background
# process so that it does not block the main thread.
def background_coordinate_descent(printer, adj_params, params, error_func):
parent_conn, child_conn = multiprocessing.Pipe()
+
def wrapper():
queuelogger.clear_bg_logging()
try:
@@ -61,6 +63,7 @@ def background_coordinate_descent(printer, adj_params, params, error_func):
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
@@ -70,10 +73,10 @@ def background_coordinate_descent(printer, adj_params, params, error_func):
gcode = 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("Working on calibration...", log=False)
- eventtime = reactor.pause(eventtime + .1)
+ eventtime = reactor.pause(eventtime + 0.1)
# Return results
is_err, res = parent_conn.recv()
if is_err:
@@ -87,6 +90,7 @@ def background_coordinate_descent(printer, adj_params, params, error_func):
# Trilateration
######################################################################
+
# Trilateration finds the intersection of three spheres. See the
# wikipedia article for the details of the algorithm.
def trilateration(sphere_coords, radius2):
@@ -95,15 +99,15 @@ def trilateration(sphere_coords, radius2):
s31 = matrix_sub(sphere_coord3, sphere_coord1)
d = math.sqrt(matrix_magsq(s21))
- ex = matrix_mul(s21, 1. / d)
+ ex = matrix_mul(s21, 1.0 / d)
i = matrix_dot(ex, s31)
vect_ey = matrix_sub(s31, matrix_mul(ex, i))
- ey = matrix_mul(vect_ey, 1. / math.sqrt(matrix_magsq(vect_ey)))
+ ey = matrix_mul(vect_ey, 1.0 / math.sqrt(matrix_magsq(vect_ey)))
ez = matrix_cross(ex, ey)
j = matrix_dot(ey, s31)
- x = (radius2[0] - radius2[1] + d**2) / (2. * d)
- y = (radius2[0] - radius2[2] - x**2 + (x-i)**2 + j**2) / (2. * j)
+ x = (radius2[0] - radius2[1] + d**2) / (2.0 * d)
+ y = (radius2[0] - radius2[2] - x**2 + (x - i) ** 2 + j**2) / (2.0 * j)
z = -math.sqrt(radius2[0] - x**2 - y**2)
ex_x = matrix_mul(ex, x)
@@ -116,37 +120,50 @@ def trilateration(sphere_coords, radius2):
# Matrix helper functions for 3x1 matrices
######################################################################
+
def matrix_cross(m1, m2):
- return [m1[1] * m2[2] - m1[2] * m2[1],
- m1[2] * m2[0] - m1[0] * m2[2],
- m1[0] * m2[1] - m1[1] * m2[0]]
+ return [
+ m1[1] * m2[2] - m1[2] * m2[1],
+ m1[2] * m2[0] - m1[0] * m2[2],
+ m1[0] * m2[1] - m1[1] * m2[0],
+ ]
+
def matrix_dot(m1, m2):
return m1[0] * m2[0] + m1[1] * m2[1] + m1[2] * m2[2]
+
def matrix_magsq(m1):
- return m1[0]**2 + m1[1]**2 + m1[2]**2
+ return m1[0] ** 2 + m1[1] ** 2 + m1[2] ** 2
+
def matrix_add(m1, m2):
return [m1[0] + m2[0], m1[1] + m2[1], m1[2] + m2[2]]
+
def matrix_sub(m1, m2):
return [m1[0] - m2[0], m1[1] - m2[1], m1[2] - m2[2]]
+
def matrix_mul(m1, s):
- return [m1[0]*s, m1[1]*s, m1[2]*s]
+ return [m1[0] * s, m1[1] * s, m1[2] * s]
+
######################################################################
# Matrix helper functions for 3x3 matrices
######################################################################
+
def matrix_det(a):
x0, x1, x2 = a
return matrix_dot(x0, matrix_cross(x1, x2))
+
def matrix_inv(a):
x0, x1, x2 = a
- inv_det = 1. / matrix_det(a)
- return [matrix_mul(matrix_cross(x1, x2), inv_det),
- matrix_mul(matrix_cross(x2, x0), inv_det),
- matrix_mul(matrix_cross(x0, x1), inv_det)]
+ inv_det = 1.0 / matrix_det(a)
+ return [
+ matrix_mul(matrix_cross(x1, x2), inv_det),
+ matrix_mul(matrix_cross(x2, x0), inv_det),
+ matrix_mul(matrix_cross(x0, x1), inv_det),
+ ]
diff --git a/klippy/mcu.py b/klippy/mcu.py
index 8b41e4e0..279c3f1d 100644
--- a/klippy/mcu.py
+++ b/klippy/mcu.py
@@ -6,6 +6,7 @@
import sys, os, zlib, logging, math
import serialhdl, msgproto, pins, chelper, clocksync
+
class error(Exception):
pass
@@ -14,10 +15,12 @@ class error(Exception):
# Command transmit helper classes
######################################################################
+
# Class to retry sending of a query command until a given response is received
class RetryAsyncCommand:
TIMEOUT_TIME = 5.0
RETRY_TIME = 0.500
+
def __init__(self, serial, name, oid=None):
self.serial = serial
self.name = name
@@ -27,14 +30,16 @@ class RetryAsyncCommand:
self.min_query_time = self.reactor.monotonic()
self.need_response = True
self.serial.register_response(self.handle_callback, name, oid)
+
def handle_callback(self, params):
- if self.need_response and params['#sent_time'] >= self.min_query_time:
+ if self.need_response and params["#sent_time"] >= self.min_query_time:
self.need_response = False
self.reactor.async_complete(self.completion, params)
+
def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0, retry=True):
- cmd, = cmds
+ (cmd,) = cmds
self.serial.raw_send_wait_ack(cmd, minclock, reqclock, cmd_queue)
- self.min_query_time = 0.
+ self.min_query_time = 0.0
timeout_time = query_time = self.reactor.monotonic()
if retry:
timeout_time += self.TIMEOUT_TIME
@@ -46,14 +51,24 @@ class RetryAsyncCommand:
query_time = self.reactor.monotonic()
if query_time > timeout_time:
self.serial.register_response(None, self.name, self.oid)
- raise serialhdl.error("Timeout on wait for '%s' response"
- % (self.name,))
+ raise serialhdl.error(
+ "Timeout on wait for '%s' response" % (self.name,)
+ )
self.serial.raw_send(cmd, minclock, minclock, cmd_queue)
+
# Wrapper around query commands
class CommandQueryWrapper:
- def __init__(self, serial, msgformat, respformat, oid=None,
- cmd_queue=None, is_async=False, error=serialhdl.error):
+ def __init__(
+ self,
+ serial,
+ msgformat,
+ respformat,
+ oid=None,
+ cmd_queue=None,
+ is_async=False,
+ error=serialhdl.error,
+ ):
self._serial = serial
self._cmd = serial.get_msgparser().lookup_command(msgformat)
serial.get_msgparser().lookup_command(respformat)
@@ -66,22 +81,25 @@ class CommandQueryWrapper:
if cmd_queue is None:
cmd_queue = serial.get_default_command_queue()
self._cmd_queue = cmd_queue
+
def _do_send(self, cmds, minclock, reqclock, retry):
xh = self._xmit_helper(self._serial, self._response, self._oid)
reqclock = max(minclock, reqclock)
try:
- return xh.get_response(cmds, self._cmd_queue, minclock, reqclock,
- retry)
+ return xh.get_response(cmds, self._cmd_queue, minclock, reqclock, retry)
except serialhdl.error as e:
raise self._error(str(e))
+
def send(self, data=(), minclock=0, reqclock=0, retry=True):
- return self._do_send([self._cmd.encode(data)], minclock, reqclock,
- retry)
- def send_with_preface(self, preface_cmd, preface_data=(), data=(),
- minclock=0, reqclock=0, retry=True):
+ return self._do_send([self._cmd.encode(data)], minclock, reqclock, retry)
+
+ def send_with_preface(
+ self, preface_cmd, preface_data=(), data=(), minclock=0, reqclock=0, retry=True
+ ):
cmds = [preface_cmd._cmd.encode(preface_data), self._cmd.encode(data)]
return self._do_send(cmds, minclock, reqclock, retry)
+
# Wrapper around command sending
class CommandWrapper:
def __init__(self, serial, msgformat, cmd_queue=None):
@@ -91,13 +109,16 @@ class CommandWrapper:
if cmd_queue is None:
cmd_queue = serial.get_default_command_queue()
self._cmd_queue = cmd_queue
- self._msgtag = msgparser.lookup_msgid(msgformat) & 0xffffffff
+ self._msgtag = msgparser.lookup_msgid(msgformat) & 0xFFFFFFFF
+
def send(self, data=(), minclock=0, reqclock=0):
cmd = self._cmd.encode(data)
self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue)
+
def send_wait_ack(self, data=(), minclock=0, reqclock=0):
cmd = self._cmd.encode(data)
self._serial.raw_send_wait_ack(cmd, minclock, reqclock, self._cmd_queue)
+
def get_command_tag(self):
return self._msgtag
@@ -106,11 +127,13 @@ class CommandWrapper:
# Wrapper classes for MCU pins
######################################################################
+
class MCU_trsync:
REASON_ENDSTOP_HIT = 1
REASON_HOST_REQUEST = 2
REASON_PAST_END_TIME = 3
REASON_COMMS_TIMEOUT = 4
+
def __init__(self, mcu, trdispatch):
self._mcu = mcu
self._trdispatch = trdispatch
@@ -127,109 +150,141 @@ class MCU_trsync:
mcu.register_config_callback(self._build_config)
printer = mcu.get_printer()
printer.register_event_handler("klippy:shutdown", self._shutdown)
+
def get_mcu(self):
return self._mcu
+
def get_oid(self):
return self._oid
+
def get_command_queue(self):
return self._cmd_queue
+
def add_stepper(self, stepper):
if stepper in self._steppers:
return
self._steppers.append(stepper)
+
def get_steppers(self):
return list(self._steppers)
+
def _build_config(self):
mcu = self._mcu
# Setup config
mcu.add_config_cmd("config_trsync oid=%d" % (self._oid,))
mcu.add_config_cmd(
"trsync_start oid=%d report_clock=0 report_ticks=0 expire_reason=0"
- % (self._oid,), on_restart=True)
+ % (self._oid,),
+ on_restart=True,
+ )
# Lookup commands
self._trsync_start_cmd = mcu.lookup_command(
- "trsync_start oid=%c report_clock=%u report_ticks=%u"
- " expire_reason=%c", cq=self._cmd_queue)
+ "trsync_start oid=%c report_clock=%u report_ticks=%u" " expire_reason=%c",
+ cq=self._cmd_queue,
+ )
self._trsync_set_timeout_cmd = mcu.lookup_command(
- "trsync_set_timeout oid=%c clock=%u", cq=self._cmd_queue)
+ "trsync_set_timeout oid=%c clock=%u", cq=self._cmd_queue
+ )
self._trsync_trigger_cmd = mcu.lookup_command(
- "trsync_trigger oid=%c reason=%c", cq=self._cmd_queue)
+ "trsync_trigger oid=%c reason=%c", cq=self._cmd_queue
+ )
self._trsync_query_cmd = mcu.lookup_query_command(
"trsync_trigger oid=%c reason=%c",
"trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u",
- oid=self._oid, cq=self._cmd_queue)
+ oid=self._oid,
+ cq=self._cmd_queue,
+ )
self._stepper_stop_cmd = mcu.lookup_command(
- "stepper_stop_on_trigger oid=%c trsync_oid=%c", cq=self._cmd_queue)
+ "stepper_stop_on_trigger oid=%c trsync_oid=%c", cq=self._cmd_queue
+ )
# Create trdispatch_mcu object
set_timeout_tag = mcu.lookup_command(
- "trsync_set_timeout oid=%c clock=%u").get_command_tag()
+ "trsync_set_timeout oid=%c clock=%u"
+ ).get_command_tag()
trigger_cmd = mcu.lookup_command("trsync_trigger oid=%c reason=%c")
trigger_tag = trigger_cmd.get_command_tag()
state_cmd = mcu.lookup_command(
- "trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u")
+ "trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u"
+ )
state_tag = state_cmd.get_command_tag()
ffi_main, ffi_lib = chelper.get_ffi()
- self._trdispatch_mcu = ffi_main.gc(ffi_lib.trdispatch_mcu_alloc(
- self._trdispatch, mcu._serial.get_serialqueue(), # XXX
- self._cmd_queue, self._oid, set_timeout_tag, trigger_tag,
- state_tag), ffi_lib.free)
+ self._trdispatch_mcu = ffi_main.gc(
+ ffi_lib.trdispatch_mcu_alloc(
+ self._trdispatch,
+ mcu._serial.get_serialqueue(), # XXX
+ self._cmd_queue,
+ self._oid,
+ set_timeout_tag,
+ trigger_tag,
+ state_tag,
+ ),
+ ffi_lib.free,
+ )
+
def _shutdown(self):
tc = self._trigger_completion
if tc is not None:
self._trigger_completion = None
tc.complete(False)
+
def _handle_trsync_state(self, params):
- if not params['can_trigger']:
+ if not params["can_trigger"]:
tc = self._trigger_completion
if tc is not None:
self._trigger_completion = None
- reason = params['trigger_reason']
- is_failure = (reason >= self.REASON_COMMS_TIMEOUT)
+ reason = params["trigger_reason"]
+ is_failure = reason >= self.REASON_COMMS_TIMEOUT
self._reactor.async_complete(tc, is_failure)
elif self._home_end_clock is not None:
- clock = self._mcu.clock32_to_clock64(params['clock'])
+ clock = self._mcu.clock32_to_clock64(params["clock"])
if clock >= self._home_end_clock:
self._home_end_clock = None
- self._trsync_trigger_cmd.send([self._oid,
- self.REASON_PAST_END_TIME])
- def start(self, print_time, report_offset,
- trigger_completion, expire_timeout):
+ self._trsync_trigger_cmd.send([self._oid, self.REASON_PAST_END_TIME])
+
+ def start(self, print_time, report_offset, trigger_completion, expire_timeout):
self._trigger_completion = trigger_completion
self._home_end_clock = None
clock = self._mcu.print_time_to_clock(print_time)
expire_ticks = self._mcu.seconds_to_clock(expire_timeout)
expire_clock = clock + expire_ticks
- report_ticks = self._mcu.seconds_to_clock(expire_timeout * .3)
- report_clock = clock + int(report_ticks * report_offset + .5)
- min_extend_ticks = int(report_ticks * .8 + .5)
+ report_ticks = self._mcu.seconds_to_clock(expire_timeout * 0.3)
+ report_clock = clock + int(report_ticks * report_offset + 0.5)
+ min_extend_ticks = int(report_ticks * 0.8 + 0.5)
ffi_main, ffi_lib = chelper.get_ffi()
- ffi_lib.trdispatch_mcu_setup(self._trdispatch_mcu, clock, expire_clock,
- expire_ticks, min_extend_ticks)
- self._mcu.register_response(self._handle_trsync_state,
- "trsync_state", self._oid)
- self._trsync_start_cmd.send([self._oid, report_clock, report_ticks,
- self.REASON_COMMS_TIMEOUT],
- reqclock=report_clock)
+ ffi_lib.trdispatch_mcu_setup(
+ self._trdispatch_mcu, clock, expire_clock, expire_ticks, min_extend_ticks
+ )
+ self._mcu.register_response(
+ self._handle_trsync_state, "trsync_state", self._oid
+ )
+ self._trsync_start_cmd.send(
+ [self._oid, report_clock, report_ticks, self.REASON_COMMS_TIMEOUT],
+ reqclock=report_clock,
+ )
for s in self._steppers:
self._stepper_stop_cmd.send([s.get_oid(), self._oid])
- self._trsync_set_timeout_cmd.send([self._oid, expire_clock],
- reqclock=expire_clock)
+ self._trsync_set_timeout_cmd.send(
+ [self._oid, expire_clock], reqclock=expire_clock
+ )
+
def set_home_end_time(self, home_end_time):
self._home_end_clock = self._mcu.print_time_to_clock(home_end_time)
+
def stop(self):
self._mcu.register_response(None, "trsync_state", self._oid)
self._trigger_completion = None
if self._mcu.is_fileoutput():
return self.REASON_ENDSTOP_HIT
- params = self._trsync_query_cmd.send([self._oid,
- self.REASON_HOST_REQUEST])
+ params = self._trsync_query_cmd.send([self._oid, self.REASON_HOST_REQUEST])
for s in self._steppers:
s.note_homing_end()
- return params['trigger_reason']
+ return params["trigger_reason"]
+
TRSYNC_TIMEOUT = 0.025
TRSYNC_SINGLE_MCU_TIMEOUT = 0.250
+
class TriggerDispatch:
def __init__(self, mcu):
self._mcu = mcu
@@ -237,10 +292,13 @@ class TriggerDispatch:
ffi_main, ffi_lib = chelper.get_ffi()
self._trdispatch = ffi_main.gc(ffi_lib.trdispatch_alloc(), ffi_lib.free)
self._trsyncs = [MCU_trsync(mcu, self._trdispatch)]
+
def get_oid(self):
return self._trsyncs[0].get_oid()
+
def get_command_queue(self):
return self._trsyncs[0].get_command_queue()
+
def add_stepper(self, stepper):
trsyncs = {trsync.get_mcu(): trsync for trsync in self._trsyncs}
trsync = trsyncs.get(stepper.get_mcu())
@@ -250,15 +308,18 @@ class TriggerDispatch:
trsync.add_stepper(stepper)
# Check for unsupported multi-mcu shared stepper rails
sname = stepper.get_name()
- if sname.startswith('stepper_'):
+ if sname.startswith("stepper_"):
for ot in self._trsyncs:
for s in ot.get_steppers():
if ot is not trsync and s.get_name().startswith(sname[:9]):
cerror = self._mcu.get_printer().config_error
- raise cerror("Multi-mcu homing not supported on"
- " multi-mcu shared axis")
+ raise cerror(
+ "Multi-mcu homing not supported on" " multi-mcu shared axis"
+ )
+
def get_steppers(self):
return [s for trsync in self._trsyncs for s in trsync.get_steppers()]
+
def start(self, print_time):
reactor = self._mcu.get_printer().get_reactor()
self._trigger_completion = reactor.completion()
@@ -267,18 +328,21 @@ class TriggerDispatch:
expire_timeout = TRSYNC_SINGLE_MCU_TIMEOUT
for i, trsync in enumerate(self._trsyncs):
report_offset = float(i) / len(self._trsyncs)
- trsync.start(print_time, report_offset,
- self._trigger_completion, expire_timeout)
+ trsync.start(
+ print_time, report_offset, self._trigger_completion, expire_timeout
+ )
etrsync = self._trsyncs[0]
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST)
return self._trigger_completion
+
def wait_end(self, end_time):
etrsync = self._trsyncs[0]
etrsync.set_home_end_time(end_time)
if self._mcu.is_fileoutput():
self._trigger_completion.complete(True)
self._trigger_completion.wait()
+
def stop(self):
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_stop(self._trdispatch)
@@ -288,53 +352,75 @@ class TriggerDispatch:
return err_res[0]
return res[0]
+
class MCU_endstop:
def __init__(self, mcu, pin_params):
self._mcu = mcu
- self._pin = pin_params['pin']
- self._pullup = pin_params['pullup']
- self._invert = pin_params['invert']
+ self._pin = pin_params["pin"]
+ self._pullup = pin_params["pullup"]
+ self._invert = pin_params["invert"]
self._oid = self._mcu.create_oid()
self._home_cmd = self._query_cmd = None
self._mcu.register_config_callback(self._build_config)
self._rest_ticks = 0
self._dispatch = TriggerDispatch(mcu)
+
def get_mcu(self):
return self._mcu
+
def add_stepper(self, stepper):
self._dispatch.add_stepper(stepper)
+
def get_steppers(self):
return self._dispatch.get_steppers()
+
def _build_config(self):
# Setup config
- self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d"
- % (self._oid, self._pin, self._pullup))
+ self._mcu.add_config_cmd(
+ "config_endstop oid=%d pin=%s pull_up=%d"
+ % (self._oid, self._pin, self._pullup)
+ )
self._mcu.add_config_cmd(
"endstop_home oid=%d clock=0 sample_ticks=0 sample_count=0"
- " rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0"
- % (self._oid,), on_restart=True)
+ " rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0" % (self._oid,),
+ on_restart=True,
+ )
# Lookup commands
cmd_queue = self._dispatch.get_command_queue()
self._home_cmd = self._mcu.lookup_command(
"endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c",
- cq=cmd_queue)
+ cq=cmd_queue,
+ )
self._query_cmd = self._mcu.lookup_query_command(
"endstop_query_state oid=%c",
"endstop_state oid=%c homing=%c next_clock=%u pin_value=%c",
- oid=self._oid, cq=cmd_queue)
- def home_start(self, print_time, sample_time, sample_count, rest_time,
- triggered=True):
+ oid=self._oid,
+ cq=cmd_queue,
+ )
+
+ def home_start(
+ self, print_time, sample_time, sample_count, rest_time, triggered=True
+ ):
clock = self._mcu.print_time_to_clock(print_time)
- rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock
+ rest_ticks = self._mcu.print_time_to_clock(print_time + rest_time) - clock
self._rest_ticks = rest_ticks
trigger_completion = self._dispatch.start(print_time)
self._home_cmd.send(
- [self._oid, clock, self._mcu.seconds_to_clock(sample_time),
- sample_count, rest_ticks, triggered ^ self._invert,
- self._dispatch.get_oid(), MCU_trsync.REASON_ENDSTOP_HIT],
- reqclock=clock)
+ [
+ self._oid,
+ clock,
+ self._mcu.seconds_to_clock(sample_time),
+ sample_count,
+ rest_ticks,
+ triggered ^ self._invert,
+ self._dispatch.get_oid(),
+ MCU_trsync.REASON_ENDSTOP_HIT,
+ ],
+ reqclock=clock,
+ )
return trigger_completion
+
def home_wait(self, home_end_time):
self._dispatch.wait_end(home_end_time)
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0])
@@ -343,100 +429,127 @@ class MCU_endstop:
cmderr = self._mcu.get_printer().command_error
raise cmderr("Communication timeout during homing")
if res != MCU_trsync.REASON_ENDSTOP_HIT:
- return 0.
+ return 0.0
if self._mcu.is_fileoutput():
return home_end_time
params = self._query_cmd.send([self._oid])
- next_clock = self._mcu.clock32_to_clock64(params['next_clock'])
+ next_clock = self._mcu.clock32_to_clock64(params["next_clock"])
return self._mcu.clock_to_print_time(next_clock - self._rest_ticks)
+
def query_endstop(self, print_time):
clock = self._mcu.print_time_to_clock(print_time)
if self._mcu.is_fileoutput():
return 0
params = self._query_cmd.send([self._oid], minclock=clock)
- return params['pin_value'] ^ self._invert
+ return params["pin_value"] ^ self._invert
+
class MCU_digital_out:
def __init__(self, mcu, pin_params):
self._mcu = mcu
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"]
self._start_value = self._shutdown_value = self._invert
- self._max_duration = 2.
+ self._max_duration = 2.0
self._last_clock = 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_start_value(self, start_value, shutdown_value):
self._start_value = (not not start_value) ^ self._invert
self._shutdown_value = (not not shutdown_value) ^ self._invert
+
def _build_config(self):
if self._max_duration and self._start_value != self._shutdown_value:
- raise pins.error("Pin with max duration must have start"
- " value equal to shutdown value")
+ raise pins.error(
+ "Pin with max duration must have start" " value equal to shutdown value"
+ )
mdur_ticks = self._mcu.seconds_to_clock(self._max_duration)
- if mdur_ticks >= 1<<31:
+ if mdur_ticks >= 1 << 31:
raise pins.error("Digital pin max duration 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,
- self._shutdown_value, mdur_ticks))
- self._mcu.add_config_cmd("update_digital_out oid=%d value=%d"
- % (self._oid, self._start_value),
- on_restart=True)
+ " max_duration=%d"
+ % (
+ self._oid,
+ self._pin,
+ self._start_value,
+ self._shutdown_value,
+ mdur_ticks,
+ )
+ )
+ self._mcu.add_config_cmd(
+ "update_digital_out oid=%d value=%d" % (self._oid, self._start_value),
+ on_restart=True,
+ )
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 set_digital(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time)
- self._set_cmd.send([self._oid, clock, (not not value) ^ self._invert],
- minclock=self._last_clock, reqclock=clock)
+ self._set_cmd.send(
+ [self._oid, clock, (not not value) ^ self._invert],
+ minclock=self._last_clock,
+ reqclock=clock,
+ )
self._last_clock = clock
+
class MCU_pwm:
def __init__(self, mcu, pin_params):
self._mcu = mcu
self._hardware_pwm = False
self._cycle_time = 0.100
- self._max_duration = 2.
+ self._max_duration = 2.0
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"]
self._start_value = self._shutdown_value = float(self._invert)
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):
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):
if self._max_duration and self._start_value != self._shutdown_value:
- raise pins.error("Pin with max duration must have start"
- " value equal to shutdown value")
+ raise pins.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)
mdur_ticks = self._mcu.seconds_to_clock(self._max_duration)
- if mdur_ticks >= 1<<31:
+ if mdur_ticks >= 1 << 31:
raise pins.error("PWM pin max duration too large")
if self._hardware_pwm:
self._pwm_max = self._mcu.get_constant_float("PWM_MAX")
@@ -445,99 +558,137 @@ class MCU_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._shutdown_value * self._pwm_max, mdur_ticks))
+ % (
+ self._oid,
+ self._pin,
+ cycle_ticks,
+ self._start_value * self._pwm_max,
+ self._shutdown_value * self._pwm_max,
+ mdur_ticks,
+ )
+ )
svalue = 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, svalue),
- on_restart=True)
+ self._mcu.add_config_cmd(
+ "queue_pwm_out oid=%d clock=%d value=%d"
+ % (self._oid, self._last_clock, svalue),
+ on_restart=True,
+ )
self._set_cmd = self._mcu.lookup_command(
- "queue_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
+ "queue_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue
+ )
return
# Software PWM
- if self._shutdown_value not in [0., 1.]:
+ if self._shutdown_value not in [0.0, 1.0]:
raise pins.error("shutdown value must be 0.0 or 1.0 on soft pwm")
- if cycle_ticks >= 1<<31:
+ if cycle_ticks >= 1 << 31:
raise pins.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, mdur_ticks))
+ % (
+ self._oid,
+ self._pin,
+ self._start_value >= 1.0,
+ self._shutdown_value >= 0.5,
+ mdur_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)
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
+ )
+
def set_pwm(self, print_time, value):
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)
clock = self._mcu.print_time_to_clock(print_time)
- self._set_cmd.send([self._oid, clock, v],
- minclock=self._last_clock, reqclock=clock)
+ self._set_cmd.send(
+ [self._oid, clock, v], minclock=self._last_clock, reqclock=clock
+ )
self._last_clock = clock
+
class MCU_adc:
def __init__(self, mcu, pin_params):
self._mcu = mcu
- self._pin = pin_params['pin']
- self._min_sample = self._max_sample = 0.
- self._sample_time = self._report_time = 0.
+ self._pin = pin_params["pin"]
+ self._min_sample = self._max_sample = 0.0
+ self._sample_time = self._report_time = 0.0
self._sample_count = self._range_check_count = 0
self._report_clock = 0
- self._last_state = (0., 0.)
+ self._last_state = (0.0, 0.0)
self._oid = self._callback = None
self._mcu.register_config_callback(self._build_config)
- self._inv_max_adc = 0.
+ self._inv_max_adc = 0.0
+
def get_mcu(self):
return self._mcu
- 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._sample_time = sample_time
self._sample_count = sample_count
self._min_sample = minval
self._max_sample = maxval
self._range_check_count = range_check_count
+
def setup_adc_callback(self, report_time, callback):
self._report_time = report_time
self._callback = callback
+
def get_last_value(self):
return self._last_state
+
def _build_config(self):
if not self._sample_count:
return
self._oid = self._mcu.create_oid()
- self._mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (
- self._oid, self._pin))
+ self._mcu.add_config_cmd(
+ "config_analog_in oid=%d pin=%s" % (self._oid, self._pin)
+ )
clock = self._mcu.get_query_slot(self._oid)
sample_ticks = self._mcu.seconds_to_clock(self._sample_time)
mcu_adc_max = self._mcu.get_constant_float("ADC_MAX")
max_adc = self._sample_count * mcu_adc_max
self._inv_max_adc = 1.0 / max_adc
self._report_clock = self._mcu.seconds_to_clock(self._report_time)
- min_sample = max(0, min(0xffff, int(self._min_sample * max_adc)))
- max_sample = max(0, min(0xffff, int(
- math.ceil(self._max_sample * max_adc))))
+ min_sample = max(0, min(0xFFFF, int(self._min_sample * max_adc)))
+ max_sample = max(0, min(0xFFFF, int(math.ceil(self._max_sample * max_adc))))
self._mcu.add_config_cmd(
"query_analog_in oid=%d clock=%d sample_ticks=%d sample_count=%d"
- " rest_ticks=%d min_value=%d max_value=%d range_check_count=%d" % (
- self._oid, clock, sample_ticks, self._sample_count,
- self._report_clock, min_sample, max_sample,
- self._range_check_count), is_init=True)
- self._mcu.register_response(self._handle_analog_in_state,
- "analog_in_state", self._oid)
+ " rest_ticks=%d min_value=%d max_value=%d range_check_count=%d"
+ % (
+ self._oid,
+ clock,
+ sample_ticks,
+ self._sample_count,
+ self._report_clock,
+ min_sample,
+ max_sample,
+ self._range_check_count,
+ ),
+ is_init=True,
+ )
+ self._mcu.register_response(
+ self._handle_analog_in_state, "analog_in_state", self._oid
+ )
+
def _handle_analog_in_state(self, params):
- last_value = params['value'] * self._inv_max_adc
- next_clock = self._mcu.clock32_to_clock64(params['next_clock'])
+ last_value = params["value"] * self._inv_max_adc
+ 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)
self._last_state = (last_value, last_read_time)
@@ -554,38 +705,43 @@ MIN_SCHEDULE_TIME = 0.100
# Maximum time all MCUs can internally schedule into the future
MAX_NOMINAL_DURATION = 3.0
+
class MCU:
error = error
+
def __init__(self, config, clocksync):
self._printer = printer = config.get_printer()
self._clocksync = clocksync
self._reactor = printer.get_reactor()
self._name = config.get_name()
- if self._name.startswith('mcu '):
+ if self._name.startswith("mcu "):
self._name = self._name[4:]
# Serial port
wp = "mcu '%s': " % (self._name)
self._serial = serialhdl.SerialReader(self._reactor, warn_prefix=wp)
self._baud = 0
self._canbus_iface = None
- canbus_uuid = config.get('canbus_uuid', None)
+ canbus_uuid = config.get("canbus_uuid", None)
if canbus_uuid is not None:
self._serialport = canbus_uuid
- self._canbus_iface = config.get('canbus_interface', 'can0')
- cbid = self._printer.load_object(config, 'canbus_ids')
+ self._canbus_iface = config.get("canbus_interface", "can0")
+ cbid = self._printer.load_object(config, "canbus_ids")
cbid.add_uuid(config, canbus_uuid, self._canbus_iface)
- self._printer.load_object(config, 'canbus_stats %s' % (self._name,))
+ self._printer.load_object(config, "canbus_stats %s" % (self._name,))
else:
- self._serialport = config.get('serial')
- if not (self._serialport.startswith("/dev/rpmsg_")
- or self._serialport.startswith("/tmp/klipper_host_")):
- self._baud = config.getint('baud', 250000, minval=2400)
+ self._serialport = config.get("serial")
+ if not (
+ self._serialport.startswith("/dev/rpmsg_")
+ or self._serialport.startswith("/tmp/klipper_host_")
+ ):
+ self._baud = config.getint("baud", 250000, minval=2400)
# Restarts
- restart_methods = [None, 'arduino', 'cheetah', 'command', 'rpi_usb']
- self._restart_method = 'command'
+ restart_methods = [None, "arduino", "cheetah", "command", "rpi_usb"]
+ self._restart_method = "command"
if self._baud:
- self._restart_method = config.getchoice('restart_method',
- restart_methods, None)
+ self._restart_method = config.getchoice(
+ "restart_method", restart_methods, None
+ )
self._reset_cmd = self._config_reset_cmd = None
self._is_mcu_bridge = False
self._emergency_stop_cmd = None
@@ -593,47 +749,50 @@ class MCU:
self._shutdown_clock = 0
self._shutdown_msg = ""
# Config building
- printer.lookup_object('pins').register_chip(self._name, self)
+ printer.lookup_object("pins").register_chip(self._name, self)
self._oid_count = 0
self._config_callbacks = []
self._config_cmds = []
self._restart_cmds = []
self._init_cmds = []
- self._mcu_freq = 0.
+ self._mcu_freq = 0.0
# Move command queuing
ffi_main, self._ffi_lib = chelper.get_ffi()
- self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025,
- minval=0.)
+ self._max_stepper_error = config.getfloat(
+ "max_stepper_error", 0.000025, minval=0.0
+ )
self._reserved_move_slots = 0
self._stepqueues = []
self._steppersync = None
self._flush_callbacks = []
# Stats
self._get_status_info = {}
- self._stats_sumsq_base = 0.
- self._mcu_tick_avg = 0.
- self._mcu_tick_stddev = 0.
- self._mcu_tick_awake = 0.
+ self._stats_sumsq_base = 0.0
+ self._mcu_tick_avg = 0.0
+ self._mcu_tick_stddev = 0.0
+ self._mcu_tick_awake = 0.0
# Register handlers
printer.load_object(config, "error_mcu")
- printer.register_event_handler("klippy:firmware_restart",
- self._firmware_restart)
- printer.register_event_handler("klippy:mcu_identify",
- self._mcu_identify)
+ printer.register_event_handler(
+ "klippy:firmware_restart", self._firmware_restart
+ )
+ printer.register_event_handler("klippy:mcu_identify", self._mcu_identify)
printer.register_event_handler("klippy:connect", self._connect)
printer.register_event_handler("klippy:shutdown", self._shutdown)
printer.register_event_handler("klippy:disconnect", self._disconnect)
printer.register_event_handler("klippy:ready", self._ready)
+
# Serial callbacks
def _handle_mcu_stats(self, params):
- count = params['count']
- tick_sum = params['sum']
+ count = params["count"]
+ tick_sum = params["sum"]
c = 1.0 / (count * self._mcu_freq)
self._mcu_tick_avg = tick_sum * c
- tick_sumsq = params['sumsq'] * self._stats_sumsq_base
- diff = count*tick_sumsq - tick_sum**2
- self._mcu_tick_stddev = c * math.sqrt(max(0., diff))
+ tick_sumsq = params["sumsq"] * self._stats_sumsq_base
+ diff = count * tick_sumsq - tick_sum**2
+ self._mcu_tick_stddev = c * math.sqrt(max(0.0, diff))
self._mcu_tick_awake = tick_sum / self._mcu_freq
+
def _handle_shutdown(self, params):
if self._is_shutdown:
return
@@ -641,73 +800,82 @@ class MCU:
clock = params.get("clock")
if clock is not None:
self._shutdown_clock = self.clock32_to_clock64(clock)
- self._shutdown_msg = msg = params['static_string_id']
- event_type = params['#name']
+ self._shutdown_msg = msg = params["static_string_id"]
+ event_type = params["#name"]
self._printer.invoke_async_shutdown(
- "MCU shutdown", {"reason": msg, "mcu": self._name,
- "event_type": event_type})
- logging.info("MCU '%s' %s: %s\n%s\n%s", self._name, event_type,
- self._shutdown_msg, self._clocksync.dump_debug(),
- self._serial.dump_debug())
+ "MCU shutdown", {"reason": msg, "mcu": self._name, "event_type": event_type}
+ )
+ logging.info(
+ "MCU '%s' %s: %s\n%s\n%s",
+ self._name,
+ event_type,
+ self._shutdown_msg,
+ self._clocksync.dump_debug(),
+ self._serial.dump_debug(),
+ )
+
def _handle_starting(self, params):
if not self._is_shutdown:
- self._printer.invoke_async_shutdown("MCU '%s' spontaneous restart"
- % (self._name,))
+ self._printer.invoke_async_shutdown(
+ "MCU '%s' spontaneous restart" % (self._name,)
+ )
+
# Connection phase
def _check_restart(self, reason):
start_reason = self._printer.get_start_args().get("start_reason")
- if start_reason == 'firmware_restart':
+ if start_reason == "firmware_restart":
return
- logging.info("Attempting automated MCU '%s' restart: %s",
- self._name, reason)
- self._printer.request_exit('firmware_restart')
+ logging.info("Attempting automated MCU '%s' restart: %s", self._name, reason)
+ self._printer.request_exit("firmware_restart")
self._reactor.pause(self._reactor.monotonic() + 2.000)
raise error("Attempt MCU '%s' restart failed" % (self._name,))
+
def _connect_file(self, pace=False):
# In a debugging mode. Open debug output file and read data dictionary
start_args = self._printer.get_start_args()
- if self._name == 'mcu':
- out_fname = start_args.get('debugoutput')
- dict_fname = start_args.get('dictionary')
+ if self._name == "mcu":
+ out_fname = start_args.get("debugoutput")
+ dict_fname = start_args.get("dictionary")
else:
- out_fname = start_args.get('debugoutput') + "-" + self._name
- dict_fname = start_args.get('dictionary_' + self._name)
- outfile = open(out_fname, 'wb')
- dfile = open(dict_fname, 'rb')
+ out_fname = start_args.get("debugoutput") + "-" + self._name
+ dict_fname = start_args.get("dictionary_" + self._name)
+ outfile = open(out_fname, "wb")
+ dfile = open(dict_fname, "rb")
dict_data = dfile.read()
dfile.close()
self._serial.connect_file(outfile, dict_data)
self._clocksync.connect_file(self._serial, pace)
# Handle pacing
if not pace:
+
def dummy_estimated_print_time(eventtime):
- return 0.
+ return 0.0
+
self.estimated_print_time = dummy_estimated_print_time
+
def _send_config(self, prev_crc):
# Build config commands
for cb in self._config_callbacks:
cb()
- self._config_cmds.insert(0, "allocate_oids count=%d"
- % (self._oid_count,))
+ self._config_cmds.insert(0, "allocate_oids count=%d" % (self._oid_count,))
# Resolve pin names
- ppins = self._printer.lookup_object('pins')
+ ppins = self._printer.lookup_object("pins")
pin_resolver = ppins.get_pin_resolver(self._name)
for cmdlist in (self._config_cmds, self._restart_cmds, self._init_cmds):
for i, cmd in enumerate(cmdlist):
cmdlist[i] = pin_resolver.update_command(cmd)
# Calculate config CRC
- encoded_config = '\n'.join(self._config_cmds).encode()
- config_crc = zlib.crc32(encoded_config) & 0xffffffff
+ encoded_config = "\n".join(self._config_cmds).encode()
+ config_crc = zlib.crc32(encoded_config) & 0xFFFFFFFF
self.add_config_cmd("finalize_config crc=%d" % (config_crc,))
if prev_crc is not None and config_crc != prev_crc:
self._check_restart("CRC mismatch")
raise error("MCU '%s' CRC does not match config" % (self._name,))
# Transmit config messages (if needed)
- self.register_response(self._handle_starting, 'starting')
+ self.register_response(self._handle_starting, "starting")
try:
if prev_crc is None:
- logging.info("Sending MCU '%s' printer configuration...",
- self._name)
+ logging.info("Sending MCU '%s' printer configuration...", self._name)
for c in self._config_cmds:
self._serial.send(c)
else:
@@ -718,26 +886,31 @@ class MCU:
self._serial.send(c)
except msgproto.enumeration_error as e:
enum_name, enum_value = e.get_enum_params()
- if enum_name == 'pin':
+ if enum_name == "pin":
# Raise pin name errors as a config error (not a protocol error)
raise self._printer.config_error(
"Pin '%s' is not a valid pin name on mcu '%s'"
- % (enum_value, self._name))
+ % (enum_value, self._name)
+ )
raise
+
def _send_get_config(self):
get_config_cmd = self.lookup_query_command(
- "get_config",
- "config is_config=%c crc=%u is_shutdown=%c move_count=%hu")
+ "get_config", "config is_config=%c crc=%u is_shutdown=%c move_count=%hu"
+ )
if self.is_fileoutput():
- return { 'is_config': 0, 'move_count': 500, 'crc': 0 }
+ return {"is_config": 0, "move_count": 500, "crc": 0}
config_params = get_config_cmd.send()
if self._is_shutdown:
- raise error("MCU '%s' error during config: %s" % (
- self._name, self._shutdown_msg))
- if config_params['is_shutdown']:
- raise error("Can not update MCU '%s' config as it is shutdown" % (
- self._name,))
+ raise error(
+ "MCU '%s' error during config: %s" % (self._name, self._shutdown_msg)
+ )
+ if config_params["is_shutdown"]:
+ raise error(
+ "Can not update MCU '%s' config as it is shutdown" % (self._name,)
+ )
return config_params
+
def _log_info(self):
msgparser = self._serial.get_msgparser()
message_count = len(msgparser.get_messages())
@@ -745,61 +918,71 @@ class MCU:
log_info = [
"Loaded MCU '%s' %d commands (%s / %s)"
% (self._name, message_count, version, build_versions),
- "MCU '%s' config: %s" % (self._name, " ".join(
- ["%s=%s" % (k, v) for k, v in self.get_constants().items()]))]
+ "MCU '%s' config: %s"
+ % (
+ self._name,
+ " ".join(["%s=%s" % (k, v) for k, v in self.get_constants().items()]),
+ ),
+ ]
return "\n".join(log_info)
+
def _connect(self):
config_params = self._send_get_config()
- if not config_params['is_config']:
- if self._restart_method == 'rpi_usb':
+ if not config_params["is_config"]:
+ if self._restart_method == "rpi_usb":
# Only configure mcu after usb power reset
self._check_restart("full reset before config")
# Not configured - send config and issue get_config again
self._send_config(None)
config_params = self._send_get_config()
- if not config_params['is_config'] and not self.is_fileoutput():
+ if not config_params["is_config"] and not self.is_fileoutput():
raise error("Unable to configure MCU '%s'" % (self._name,))
else:
start_reason = self._printer.get_start_args().get("start_reason")
- if start_reason == 'firmware_restart':
- raise error("Failed automated reset of MCU '%s'"
- % (self._name,))
+ if start_reason == "firmware_restart":
+ raise error("Failed automated reset of MCU '%s'" % (self._name,))
# Already configured - send init commands
- self._send_config(config_params['crc'])
+ self._send_config(config_params["crc"])
# Setup steppersync with the move_count returned by get_config
- move_count = config_params['move_count']
+ move_count = config_params["move_count"]
if move_count < self._reserved_move_slots:
raise error("Too few moves available on MCU '%s'" % (self._name,))
ffi_main, ffi_lib = chelper.get_ffi()
self._steppersync = ffi_main.gc(
- ffi_lib.steppersync_alloc(self._serial.get_serialqueue(),
- self._stepqueues, len(self._stepqueues),
- move_count-self._reserved_move_slots),
- ffi_lib.steppersync_free)
- ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
+ ffi_lib.steppersync_alloc(
+ self._serial.get_serialqueue(),
+ self._stepqueues,
+ len(self._stepqueues),
+ move_count - self._reserved_move_slots,
+ ),
+ ffi_lib.steppersync_free,
+ )
+ ffi_lib.steppersync_set_time(self._steppersync, 0.0, self._mcu_freq)
# Log config information
move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count)
logging.info(move_msg)
log_info = self._log_info() + "\n" + move_msg
self._printer.set_rollover_info(self._name, log_info, log=False)
+
def _mcu_identify(self):
if self.is_fileoutput():
self._connect_file()
else:
resmeth = self._restart_method
- if resmeth == 'rpi_usb' and not os.path.exists(self._serialport):
+ if resmeth == "rpi_usb" and not os.path.exists(self._serialport):
# Try toggling usb power
self._check_restart("enable power")
try:
if self._canbus_iface is not None:
- cbid = self._printer.lookup_object('canbus_ids')
+ cbid = self._printer.lookup_object("canbus_ids")
nodeid = cbid.get_nodeid(self._serialport)
- self._serial.connect_canbus(self._serialport, nodeid,
- self._canbus_iface)
+ self._serial.connect_canbus(
+ self._serialport, nodeid, self._canbus_iface
+ )
elif self._baud:
# Cheetah boards require RTS to be deasserted
# else a reset will trigger the built-in bootloader.
- rts = (resmeth != "cheetah")
+ rts = resmeth != "cheetah"
self._serial.connect_uart(self._serialport, self._baud, rts)
else:
self._serial.connect_pipe(self._serialport)
@@ -807,33 +990,35 @@ class MCU:
except serialhdl.error as e:
raise error(str(e))
logging.info(self._log_info())
- ppins = self._printer.lookup_object('pins')
+ ppins = self._printer.lookup_object("pins")
pin_resolver = ppins.get_pin_resolver(self._name)
for cname, value in self.get_constants().items():
if cname.startswith("RESERVE_PINS_"):
- for pin in value.split(','):
+ for pin in value.split(","):
pin_resolver.reserve_pin(pin, cname[13:])
- self._mcu_freq = self.get_constant_float('CLOCK_FREQ')
- self._stats_sumsq_base = self.get_constant_float('STATS_SUMSQ_BASE')
+ self._mcu_freq = self.get_constant_float("CLOCK_FREQ")
+ self._stats_sumsq_base = self.get_constant_float("STATS_SUMSQ_BASE")
self._emergency_stop_cmd = self.lookup_command("emergency_stop")
self._reset_cmd = self.try_lookup_command("reset")
self._config_reset_cmd = self.try_lookup_command("config_reset")
ext_only = self._reset_cmd is None and self._config_reset_cmd is None
msgparser = self._serial.get_msgparser()
- mbaud = msgparser.get_constant('SERIAL_BAUD', None)
+ mbaud = msgparser.get_constant("SERIAL_BAUD", None)
if self._restart_method is None and mbaud is None and not ext_only:
- self._restart_method = 'command'
- if msgparser.get_constant('CANBUS_BRIDGE', 0):
+ self._restart_method = "command"
+ if msgparser.get_constant("CANBUS_BRIDGE", 0):
self._is_mcu_bridge = True
- self._printer.register_event_handler("klippy:firmware_restart",
- self._firmware_restart_bridge)
+ self._printer.register_event_handler(
+ "klippy:firmware_restart", self._firmware_restart_bridge
+ )
version, build_versions = msgparser.get_version_info()
- self._get_status_info['mcu_version'] = version
- self._get_status_info['mcu_build_versions'] = build_versions
- self._get_status_info['mcu_constants'] = msgparser.get_constants()
- self.register_response(self._handle_shutdown, 'shutdown')
- self.register_response(self._handle_shutdown, 'is_shutdown')
- self.register_response(self._handle_mcu_stats, 'stats')
+ self._get_status_info["mcu_version"] = version
+ self._get_status_info["mcu_build_versions"] = build_versions
+ self._get_status_info["mcu_constants"] = msgparser.get_constants()
+ self.register_response(self._handle_shutdown, "shutdown")
+ self.register_response(self._handle_shutdown, "is_shutdown")
+ self.register_response(self._handle_mcu_stats, "stats")
+
def _ready(self):
if self.is_fileoutput():
return
@@ -843,25 +1028,36 @@ class MCU:
get_clock = self._clocksync.get_clock
calc_freq = get_clock(systime + 1) - get_clock(systime)
freq_diff = abs(mcu_freq - calc_freq)
- mcu_freq_mhz = int(mcu_freq / 1000000. + 0.5)
- calc_freq_mhz = int(calc_freq / 1000000. + 0.5)
- if freq_diff > mcu_freq*0.01 and mcu_freq_mhz != calc_freq_mhz:
- pconfig = self._printer.lookup_object('configfile')
- msg = ("MCU '%s' configured for %dMhz but running at %dMhz!"
- % (self._name, mcu_freq_mhz, calc_freq_mhz))
+ mcu_freq_mhz = int(mcu_freq / 1000000.0 + 0.5)
+ calc_freq_mhz = int(calc_freq / 1000000.0 + 0.5)
+ if freq_diff > mcu_freq * 0.01 and mcu_freq_mhz != calc_freq_mhz:
+ pconfig = self._printer.lookup_object("configfile")
+ msg = "MCU '%s' configured for %dMhz but running at %dMhz!" % (
+ self._name,
+ mcu_freq_mhz,
+ calc_freq_mhz,
+ )
pconfig.runtime_warning(msg)
+
# Config creation helpers
def setup_pin(self, pin_type, pin_params):
- pcs = {'endstop': MCU_endstop,
- 'digital_out': MCU_digital_out, 'pwm': MCU_pwm, 'adc': MCU_adc}
+ pcs = {
+ "endstop": MCU_endstop,
+ "digital_out": MCU_digital_out,
+ "pwm": MCU_pwm,
+ "adc": MCU_adc,
+ }
if pin_type not in pcs:
raise pins.error("pin type %s not supported on mcu" % (pin_type,))
return pcs[pin_type](self, pin_params)
+
def create_oid(self):
self._oid_count += 1
return self._oid_count - 1
+
def register_config_callback(self, cb):
self._config_callbacks.append(cb)
+
def add_config_cmd(self, cmd, is_init=False, on_restart=False):
if is_init:
self._init_cmds.append(cmd)
@@ -869,74 +1065,105 @@ class MCU:
self._restart_cmds.append(cmd)
else:
self._config_cmds.append(cmd)
+
def get_query_slot(self, oid):
- slot = self.seconds_to_clock(oid * .01)
+ slot = self.seconds_to_clock(oid * 0.01)
t = int(self.estimated_print_time(self._reactor.monotonic()) + 1.5)
return self.print_time_to_clock(t) + slot
+
def seconds_to_clock(self, time):
return int(time * self._mcu_freq)
+
def get_max_stepper_error(self):
return self._max_stepper_error
+
def min_schedule_time(self):
return MIN_SCHEDULE_TIME
+
def max_nominal_duration(self):
return MAX_NOMINAL_DURATION
+
# Wrapper functions
def get_printer(self):
return self._printer
+
def get_name(self):
return self._name
+
def register_response(self, cb, msg, oid=None):
self._serial.register_response(cb, msg, oid)
+
def alloc_command_queue(self):
return self._serial.alloc_command_queue()
+
def lookup_command(self, msgformat, cq=None):
return CommandWrapper(self._serial, msgformat, cq)
- def lookup_query_command(self, msgformat, respformat, oid=None,
- cq=None, is_async=False):
- return CommandQueryWrapper(self._serial, msgformat, respformat, oid,
- cq, is_async, self._printer.command_error)
+
+ def lookup_query_command(
+ self, msgformat, respformat, oid=None, cq=None, is_async=False
+ ):
+ return CommandQueryWrapper(
+ self._serial,
+ msgformat,
+ respformat,
+ oid,
+ cq,
+ is_async,
+ self._printer.command_error,
+ )
+
def try_lookup_command(self, msgformat):
try:
return self.lookup_command(msgformat)
except self._serial.get_msgparser().error as e:
return None
+
def get_enumerations(self):
return self._serial.get_msgparser().get_enumerations()
+
def get_constants(self):
return self._serial.get_msgparser().get_constants()
+
def get_constant_float(self, name):
return self._serial.get_msgparser().get_constant_float(name)
+
def print_time_to_clock(self, print_time):
return self._clocksync.print_time_to_clock(print_time)
+
def clock_to_print_time(self, clock):
return self._clocksync.clock_to_print_time(clock)
+
def estimated_print_time(self, eventtime):
return self._clocksync.estimated_print_time(eventtime)
+
def clock32_to_clock64(self, clock32):
return self._clocksync.clock32_to_clock64(clock32)
+
# Restarts
def _disconnect(self):
self._serial.disconnect()
self._steppersync = None
+
def _shutdown(self, force=False):
- if (self._emergency_stop_cmd is None
- or (self._is_shutdown and not force)):
+ if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
return
self._emergency_stop_cmd.send()
+
def _restart_arduino(self):
logging.info("Attempting MCU '%s' reset", self._name)
self._disconnect()
serialhdl.arduino_reset(self._serialport, self._reactor)
+
def _restart_cheetah(self):
logging.info("Attempting MCU '%s' Cheetah-style reset", self._name)
self._disconnect()
serialhdl.cheetah_reset(self._serialport, self._reactor)
+
def _restart_via_command(self):
- if ((self._reset_cmd is None and self._config_reset_cmd is None)
- or not self._clocksync.is_active()):
- logging.info("Unable to issue reset command on MCU '%s'",
- self._name)
+ if (
+ self._reset_cmd is None and self._config_reset_cmd is None
+ ) or not self._clocksync.is_active():
+ logging.info("Unable to issue reset command on MCU '%s'", self._name)
return
if self._reset_cmd is None:
# Attempt reset via config_reset command
@@ -951,32 +1178,39 @@ class MCU:
self._reset_cmd.send()
self._reactor.pause(self._reactor.monotonic() + 0.015)
self._disconnect()
+
def _restart_rpi_usb(self):
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
self._disconnect()
chelper.run_hub_ctrl(0)
- self._reactor.pause(self._reactor.monotonic() + 2.)
+ self._reactor.pause(self._reactor.monotonic() + 2.0)
chelper.run_hub_ctrl(1)
+
def _firmware_restart(self, force=False):
if self._is_mcu_bridge and not force:
return
- if self._restart_method == 'rpi_usb':
+ if self._restart_method == "rpi_usb":
self._restart_rpi_usb()
- elif self._restart_method == 'command':
+ elif self._restart_method == "command":
self._restart_via_command()
- elif self._restart_method == 'cheetah':
+ elif self._restart_method == "cheetah":
self._restart_cheetah()
else:
self._restart_arduino()
+
def _firmware_restart_bridge(self):
self._firmware_restart(True)
+
# Move queue tracking
def register_stepqueue(self, stepqueue):
self._stepqueues.append(stepqueue)
+
def request_move_queue_slot(self):
self._reserved_move_slots += 1
+
def register_flush_callback(self, callback):
self._flush_callbacks.append(callback)
+
def flush_moves(self, print_time, clear_history_time):
if self._steppersync is None:
return
@@ -985,55 +1219,66 @@ class MCU:
return
for cb in self._flush_callbacks:
cb(print_time, clock)
- clear_history_clock = \
- max(0, self.print_time_to_clock(clear_history_time))
- ret = self._ffi_lib.steppersync_flush(self._steppersync, clock,
- clear_history_clock)
+ clear_history_clock = max(0, self.print_time_to_clock(clear_history_time))
+ ret = self._ffi_lib.steppersync_flush(
+ self._steppersync, clock, clear_history_clock
+ )
if ret:
- raise error("Internal error in MCU '%s' stepcompress"
- % (self._name,))
+ raise error("Internal error in MCU '%s' stepcompress" % (self._name,))
+
def check_active(self, print_time, eventtime):
if self._steppersync is None:
return
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
- if (self._clocksync.is_active() or self.is_fileoutput()
- or self._is_timeout):
+ if self._clocksync.is_active() or self.is_fileoutput() or self._is_timeout:
return
self._is_timeout = True
- logging.info("Timeout with MCU '%s' (eventtime=%f)",
- self._name, eventtime)
- self._printer.invoke_shutdown("Lost communication with MCU '%s'" % (
- self._name,))
+ logging.info("Timeout with MCU '%s' (eventtime=%f)", self._name, eventtime)
+ self._printer.invoke_shutdown(
+ "Lost communication with MCU '%s'" % (self._name,)
+ )
+
# Misc external commands
def is_fileoutput(self):
- return self._printer.get_start_args().get('debugoutput') is not None
+ return self._printer.get_start_args().get("debugoutput") is not None
+
def is_shutdown(self):
return self._is_shutdown
+
def get_shutdown_clock(self):
return self._shutdown_clock
+
def get_status(self, eventtime=None):
return dict(self._get_status_info)
+
def stats(self, eventtime):
load = "mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
- self._mcu_tick_awake, self._mcu_tick_avg, self._mcu_tick_stddev)
- stats = ' '.join([load, self._serial.stats(eventtime),
- self._clocksync.stats(eventtime)])
- parts = [s.split('=', 1) for s in stats.split()]
- last_stats = {k:(float(v) if '.' in v else int(v)) for k, v in parts}
- self._get_status_info['last_stats'] = last_stats
- return False, '%s: %s' % (self._name, stats)
+ self._mcu_tick_awake,
+ self._mcu_tick_avg,
+ self._mcu_tick_stddev,
+ )
+ stats = " ".join(
+ [load, self._serial.stats(eventtime), self._clocksync.stats(eventtime)]
+ )
+ parts = [s.split("=", 1) for s in stats.split()]
+ last_stats = {k: (float(v) if "." in v else int(v)) for k, v in parts}
+ self._get_status_info["last_stats"] = last_stats
+ return False, "%s: %s" % (self._name, stats)
+
def add_printer_objects(config):
printer = config.get_printer()
reactor = printer.get_reactor()
mainsync = clocksync.ClockSync(reactor)
- printer.add_object('mcu', MCU(config.getsection('mcu'), mainsync))
- for s in config.get_prefix_sections('mcu '):
- printer.add_object(s.section, MCU(
- s, clocksync.SecondarySync(reactor, mainsync)))
+ printer.add_object("mcu", MCU(config.getsection("mcu"), mainsync))
+ for s in config.get_prefix_sections("mcu "):
+ printer.add_object(
+ s.section, MCU(s, clocksync.SecondarySync(reactor, mainsync))
+ )
+
def get_printer_mcu(printer, name):
- if name == 'mcu':
+ if name == "mcu":
return printer.lookup_object(name)
- return printer.lookup_object('mcu ' + name)
+ return printer.lookup_object("mcu " + name)
diff --git a/klippy/msgproto.py b/klippy/msgproto.py
index 25701df3..a2151a68 100644
--- a/klippy/msgproto.py
+++ b/klippy/msgproto.py
@@ -12,101 +12,131 @@ DefaultMessages = {
MESSAGE_MIN = 5
MESSAGE_MAX = 64
-MESSAGE_HEADER_SIZE = 2
+MESSAGE_HEADER_SIZE = 2
MESSAGE_TRAILER_SIZE = 3
MESSAGE_POS_LEN = 0
MESSAGE_POS_SEQ = 1
-MESSAGE_TRAILER_CRC = 3
+MESSAGE_TRAILER_CRC = 3
MESSAGE_TRAILER_SYNC = 1
MESSAGE_PAYLOAD_MAX = MESSAGE_MAX - MESSAGE_MIN
-MESSAGE_SEQ_MASK = 0x0f
+MESSAGE_SEQ_MASK = 0x0F
MESSAGE_DEST = 0x10
-MESSAGE_SYNC = 0x7e
+MESSAGE_SYNC = 0x7E
+
class error(Exception):
pass
+
def crc16_ccitt(buf):
- crc = 0xffff
+ crc = 0xFFFF
for data in buf:
- data ^= crc & 0xff
- data ^= (data & 0x0f) << 4
+ data ^= crc & 0xFF
+ data ^= (data & 0x0F) << 4
crc = ((data << 8) | (crc >> 8)) ^ (data >> 4) ^ (data << 3)
- return [crc >> 8, crc & 0xff]
+ return [crc >> 8, crc & 0xFF]
+
class PT_uint32:
is_int = True
is_dynamic_string = False
max_length = 5
signed = False
+
def encode(self, out, v):
- if v >= 0xc000000 or v < -0x4000000: out.append((v>>28) & 0x7f | 0x80)
- if v >= 0x180000 or v < -0x80000: out.append((v>>21) & 0x7f | 0x80)
- if v >= 0x3000 or v < -0x1000: out.append((v>>14) & 0x7f | 0x80)
- if v >= 0x60 or v < -0x20: out.append((v>>7) & 0x7f | 0x80)
- out.append(v & 0x7f)
+ if v >= 0xC000000 or v < -0x4000000:
+ out.append((v >> 28) & 0x7F | 0x80)
+ if v >= 0x180000 or v < -0x80000:
+ out.append((v >> 21) & 0x7F | 0x80)
+ if v >= 0x3000 or v < -0x1000:
+ out.append((v >> 14) & 0x7F | 0x80)
+ if v >= 0x60 or v < -0x20:
+ out.append((v >> 7) & 0x7F | 0x80)
+ out.append(v & 0x7F)
+
def parse(self, s, pos):
c = s[pos]
pos += 1
- v = c & 0x7f
+ v = c & 0x7F
if (c & 0x60) == 0x60:
v |= -0x20
while c & 0x80:
c = s[pos]
pos += 1
- v = (v<<7) | (c & 0x7f)
+ v = (v << 7) | (c & 0x7F)
if not self.signed:
- v = int(v & 0xffffffff)
+ v = int(v & 0xFFFFFFFF)
return v, pos
+
class PT_int32(PT_uint32):
signed = True
+
+
class PT_uint16(PT_uint32):
max_length = 3
+
+
class PT_int16(PT_int32):
signed = True
max_length = 3
+
+
class PT_byte(PT_uint32):
max_length = 2
+
class PT_string:
is_int = False
is_dynamic_string = True
max_length = 64
+
def encode(self, out, v):
out.append(len(v))
out.extend(bytearray(v))
+
def parse(self, s, pos):
l = s[pos]
- return bytes(bytearray(s[pos+1:pos+l+1])), pos+l+1
+ return bytes(bytearray(s[pos + 1 : pos + l + 1])), pos + l + 1
+
+
class PT_progmem_buffer(PT_string):
pass
+
+
class PT_buffer(PT_string):
pass
+
class enumeration_error(error):
def __init__(self, enum_name, value):
self.enum_name = enum_name
self.value = value
- error.__init__(self, "Unknown value '%s' in enumeration '%s'"
- % (value, enum_name))
+ error.__init__(
+ self, "Unknown value '%s' in enumeration '%s'" % (value, enum_name)
+ )
+
def get_enum_params(self):
return self.enum_name, self.value
+
class Enumeration:
is_int = False
is_dynamic_string = False
+
def __init__(self, pt, enum_name, enums):
self.pt = pt
self.max_length = pt.max_length
self.enum_name = enum_name
self.enums = enums
self.reverse_enums = {v: k for k, v in enums.items()}
+
def encode(self, out, v):
tv = self.enums.get(v)
if tv is None:
raise enumeration_error(self.enum_name, v)
self.pt.encode(out, tv)
+
def parse(self, s, pos):
v, pos = self.pt.parse(s, pos)
tv = self.reverse_enums.get(v)
@@ -114,51 +144,60 @@ class Enumeration:
tv = "?%d" % (v,)
return tv, pos
+
MessageTypes = {
- '%u': PT_uint32(), '%i': PT_int32(),
- '%hu': PT_uint16(), '%hi': PT_int16(),
- '%c': PT_byte(),
- '%s': PT_string(), '%.*s': PT_progmem_buffer(), '%*s': PT_buffer(),
+ "%u": PT_uint32(),
+ "%i": PT_int32(),
+ "%hu": PT_uint16(),
+ "%hi": PT_int16(),
+ "%c": PT_byte(),
+ "%s": PT_string(),
+ "%.*s": PT_progmem_buffer(),
+ "%*s": PT_buffer(),
}
+
# Lookup the message types for a format string
def lookup_params(msgformat, enumerations={}):
out = []
- argparts = [arg.split('=') for arg in msgformat.split()[1:]]
+ argparts = [arg.split("=") for arg in msgformat.split()[1:]]
for name, fmt in argparts:
pt = MessageTypes[fmt]
for enum_name, enums in enumerations.items():
- if name == enum_name or name.endswith('_' + enum_name):
+ if name == enum_name or name.endswith("_" + enum_name):
pt = Enumeration(pt, enum_name, enums)
break
out.append((name, pt))
return out
+
# Lookup the message types for a debugging "output()" format string
def lookup_output_params(msgformat):
param_types = []
args = msgformat
while 1:
- pos = args.find('%')
+ pos = args.find("%")
if pos < 0:
break
- if pos+1 >= len(args) or args[pos+1] != '%':
+ if pos + 1 >= len(args) or args[pos + 1] != "%":
for i in range(4):
- t = MessageTypes.get(args[pos:pos+1+i])
+ t = MessageTypes.get(args[pos : pos + 1 + i])
if t is not None:
param_types.append(t)
break
else:
raise error("Invalid output format for '%s'" % (msgformat,))
- args = args[pos+1:]
+ args = args[pos + 1 :]
return param_types
+
# Update the message format to be compatible with python's % operator
def convert_msg_format(msgformat):
- for c in ['%u', '%i', '%hu', '%hi', '%c', '%.*s', '%*s']:
- msgformat = msgformat.replace(c, '%s')
+ for c in ["%u", "%i", "%hu", "%hi", "%c", "%.*s", "%*s"]:
+ msgformat = msgformat.replace(c, "%s")
return msgformat
+
class MessageFormat:
def __init__(self, msgid_bytes, msgformat, enumerations={}):
self.msgid_bytes = msgid_bytes
@@ -168,16 +207,19 @@ class MessageFormat:
self.param_names = lookup_params(msgformat, enumerations)
self.param_types = [t for name, t in self.param_names]
self.name_to_type = dict(self.param_names)
+
def encode(self, params):
out = list(self.msgid_bytes)
for i, t in enumerate(self.param_types):
t.encode(out, params[i])
return out
+
def encode_by_name(self, **params):
out = list(self.msgid_bytes)
for name, t in self.param_names:
t.encode(out, params[name])
return out
+
def parse(self, s, pos):
pos += len(self.msgid_bytes)
out = {}
@@ -185,6 +227,7 @@ class MessageFormat:
v, pos = t.parse(s, pos)
out[name] = v
return out, pos
+
def format_params(self, params):
out = []
for name, t in self.param_names:
@@ -194,13 +237,16 @@ class MessageFormat:
out.append(v)
return self.debugformat % tuple(out)
+
class OutputFormat:
- name = '#output'
+ name = "#output"
+
def __init__(self, msgid_bytes, msgformat):
self.msgid_bytes = msgid_bytes
self.msgformat = msgformat
self.debugformat = convert_msg_format(msgformat)
self.param_types = lookup_output_params(msgformat)
+
def parse(self, s, pos):
pos += len(self.msgid_bytes)
out = []
@@ -210,21 +256,27 @@ class OutputFormat:
v = repr(v)
out.append(v)
outmsg = self.debugformat % tuple(out)
- return {'#msg': outmsg}, pos
+ return {"#msg": outmsg}, pos
+
def format_params(self, params):
- return "#output %s" % (params['#msg'],)
+ return "#output %s" % (params["#msg"],)
+
class UnknownFormat:
- name = '#unknown'
+ name = "#unknown"
+
def parse(self, s, pos):
msgid, param_pos = PT_int32().parse(s, pos)
msg = bytes(bytearray(s))
- return {'#msgid': msgid, '#msg': msg}, len(s)-MESSAGE_TRAILER_SIZE
+ return {"#msgid": msgid, "#msg": msg}, len(s) - MESSAGE_TRAILER_SIZE
+
def format_params(self, params):
- return "#unknown %s" % (repr(params['#msg']),)
+ return "#unknown %s" % (repr(params["#msg"]),)
+
class MessageParser:
error = error
+
def __init__(self, warn_prefix=""):
self.warn_prefix = warn_prefix
self.unknown = UnknownFormat()
@@ -238,8 +290,10 @@ class MessageParser:
self.version = self.build_versions = ""
self.raw_identify_data = ""
self._init_messages(DefaultMessages)
+
def _error(self, msg, *params):
raise error(self.warn_prefix + (msg % params))
+
def check_packet(self, s):
if len(s) < MESSAGE_MIN:
return 0
@@ -252,14 +306,15 @@ class MessageParser:
if len(s) < msglen:
# Need more data
return 0
- if s[msglen-MESSAGE_TRAILER_SYNC] != MESSAGE_SYNC:
+ if s[msglen - MESSAGE_TRAILER_SYNC] != MESSAGE_SYNC:
return -1
- msgcrc = s[msglen-MESSAGE_TRAILER_CRC:msglen-MESSAGE_TRAILER_CRC+2]
- crc = crc16_ccitt(s[:msglen-MESSAGE_TRAILER_SIZE])
+ msgcrc = s[msglen - MESSAGE_TRAILER_CRC : msglen - MESSAGE_TRAILER_CRC + 2]
+ crc = crc16_ccitt(s[: msglen - MESSAGE_TRAILER_SIZE])
if crc != list(msgcrc):
- #logging.debug("got crc %s vs %s", repr(crc), repr(msgcrc))
+ # logging.debug("got crc %s vs %s", repr(crc), repr(msgcrc))
return -1
return msglen
+
def dump(self, s):
msgseq = s[MESSAGE_POS_SEQ]
out = ["seq: %02x" % (msgseq,)]
@@ -269,26 +324,29 @@ class MessageParser:
mid = self.messages_by_id.get(msgid, self.unknown)
params, pos = mid.parse(s, pos)
out.append(mid.format_params(params))
- if pos >= len(s)-MESSAGE_TRAILER_SIZE:
+ if pos >= len(s) - MESSAGE_TRAILER_SIZE:
break
return out
+
def format_params(self, params):
- name = params.get('#name')
+ name = params.get("#name")
mid = self.messages_by_name.get(name)
if mid is not None:
return mid.format_params(params)
- msg = params.get('#msg')
+ msg = params.get("#msg")
if msg is not None:
return "%s %s" % (name, msg)
return str(params)
+
def parse(self, s):
msgid, param_pos = self.msgid_parser.parse(s, MESSAGE_HEADER_SIZE)
mid = self.messages_by_id.get(msgid, self.unknown)
params, pos = mid.parse(s, MESSAGE_HEADER_SIZE)
- if pos != len(s)-MESSAGE_TRAILER_SIZE:
+ if pos != len(s) - MESSAGE_TRAILER_SIZE:
self._error("Extra data at end of message")
- params['#name'] = mid.name
+ params["#name"] = mid.name
return params
+
def encode_msgblock(self, seq, cmd):
msglen = MESSAGE_MIN + len(cmd)
seq = (seq & MESSAGE_SEQ_MASK) | MESSAGE_DEST
@@ -296,16 +354,18 @@ class MessageParser:
out.append(crc16_ccitt(out))
out.append(MESSAGE_SYNC)
return out
+
def _parse_buffer(self, value):
if not value:
return []
tval = int(value, 16)
out = []
for i in range(len(value) // 2):
- out.append(tval & 0xff)
+ out.append(tval & 0xFF)
tval >>= 8
out.reverse()
return out
+
def lookup_command(self, msgformat):
parts = msgformat.strip().split()
msgname = parts[0]
@@ -313,14 +373,15 @@ class MessageParser:
if mp is None:
self._error("Unknown command: %s", msgname)
if msgformat != mp.msgformat:
- self._error("Command format mismatch: %s vs %s",
- msgformat, mp.msgformat)
+ self._error("Command format mismatch: %s vs %s", msgformat, mp.msgformat)
return mp
+
def lookup_msgid(self, msgformat):
msgid = self.msgid_by_format.get(msgformat)
if msgid is None:
self._error("Unknown command: %s", msgformat)
return msgid
+
def create_command(self, msg):
parts = msg.strip().split()
if not parts:
@@ -330,7 +391,7 @@ class MessageParser:
if mp is None:
self._error("Unknown command: %s", msgname)
try:
- argparts = dict(arg.split('=', 1) for arg in parts[1:])
+ argparts = dict(arg.split("=", 1) for arg in parts[1:])
for name, value in argparts.items():
t = mp.name_to_type[name]
if t.is_dynamic_string:
@@ -343,16 +404,17 @@ class MessageParser:
except error as e:
raise
except:
- #logging.exception("Unable to extract params")
+ # logging.exception("Unable to extract params")
self._error("Unable to extract params from: %s", msgname)
try:
cmd = mp.encode_by_name(**argparts)
except error as e:
raise
except:
- #logging.exception("Unable to encode")
+ # logging.exception("Unable to encode")
self._error("Unable to encode: %s", msgname)
return cmd
+
def fill_enumerations(self, enumerations):
for add_name, add_enums in enumerations.items():
enums = self.enumerations.setdefault(add_name, {})
@@ -367,62 +429,70 @@ class MessageParser:
enum_root = enum_root[:-1]
start_enum = 0
if len(enum_root) != len(enum):
- start_enum = int(enum[len(enum_root):])
+ start_enum = int(enum[len(enum_root) :])
start_value, count = value
for i in range(count):
enums[enum_root + str(start_enum + i)] = start_value + i
+
def _init_messages(self, messages, command_ids=[], output_ids=[]):
for msgformat, msgid in messages.items():
- msgtype = 'response'
+ msgtype = "response"
if msgid in command_ids:
- msgtype = 'command'
+ msgtype = "command"
elif msgid in output_ids:
- msgtype = 'output'
+ msgtype = "output"
self.messages.append((msgid, msgtype, msgformat))
self.msgid_by_format[msgformat] = msgid
msgid_bytes = []
self.msgid_parser.encode(msgid_bytes, msgid)
- if msgtype == 'output':
- self.messages_by_id[msgid] = OutputFormat(msgid_bytes,
- msgformat)
+ if msgtype == "output":
+ self.messages_by_id[msgid] = OutputFormat(msgid_bytes, msgformat)
else:
msg = MessageFormat(msgid_bytes, msgformat, self.enumerations)
self.messages_by_id[msgid] = msg
self.messages_by_name[msg.name] = msg
+
def process_identify(self, data, decompress=True):
try:
if decompress:
data = zlib.decompress(data)
self.raw_identify_data = data
data = json.loads(data)
- self.fill_enumerations(data.get('enumerations', {}))
- commands = data.get('commands')
- responses = data.get('responses')
- output = data.get('output', {})
+ self.fill_enumerations(data.get("enumerations", {}))
+ commands = data.get("commands")
+ responses = data.get("responses")
+ output = data.get("output", {})
all_messages = dict(commands)
all_messages.update(responses)
all_messages.update(output)
- self._init_messages(all_messages, commands.values(),
- output.values())
- self.config.update(data.get('config', {}))
- self.version = data.get('version', '')
- self.build_versions = data.get('build_versions', '')
+ self._init_messages(all_messages, commands.values(), output.values())
+ self.config.update(data.get("config", {}))
+ self.version = data.get("version", "")
+ self.build_versions = data.get("build_versions", "")
except error as e:
raise
except Exception as e:
logging.exception("process_identify error")
self._error("Error during identify: %s", str(e))
+
def get_raw_data_dictionary(self):
return self.raw_identify_data
+
def get_version_info(self):
return self.version, self.build_versions
+
def get_messages(self):
return list(self.messages)
+
def get_enumerations(self):
return dict(self.enumerations)
+
def get_constants(self):
return dict(self.config)
- class sentinel: pass
+
+ class sentinel:
+ pass
+
def get_constant(self, name, default=sentinel, parser=str):
if name not in self.config:
if default is not self.sentinel:
@@ -431,10 +501,13 @@ class MessageParser:
try:
value = parser(self.config[name])
except:
- self._error("Unable to parse firmware constant %s: %s",
- name, self.config[name])
+ self._error(
+ "Unable to parse firmware constant %s: %s", name, self.config[name]
+ )
return value
+
def get_constant_float(self, name, default=sentinel):
return self.get_constant(name, default, parser=float)
+
def get_constant_int(self, name, default=sentinel):
return self.get_constant(name, default, parser=int)
diff --git a/klippy/parsedump.py b/klippy/parsedump.py
index 6d419183..93d8d14e 100755
--- a/klippy/parsedump.py
+++ b/klippy/parsedump.py
@@ -7,12 +7,14 @@
import os, sys, logging
import msgproto
+
def read_dictionary(filename):
- dfile = open(filename, 'rb')
+ dfile = open(filename, "rb")
dictionary = dfile.read()
dfile.close()
return dictionary
+
def main():
dict_filename, data_filename = sys.argv[1:]
@@ -21,7 +23,7 @@ def main():
mp = msgproto.MessageParser()
mp.process_identify(dictionary, decompress=False)
- f = open(data_filename, 'rb')
+ f = open(data_filename, "rb")
fd = f.fileno()
data = bytearray()
while 1:
@@ -38,8 +40,9 @@ def main():
data = data[-l:]
continue
msgs = mp.dump(data[:l])
- sys.stdout.write('\n'.join(msgs[1:]) + '\n')
+ sys.stdout.write("\n".join(msgs[1:]) + "\n")
data = data[l:]
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/klippy/pins.py b/klippy/pins.py
index 35fc58a2..ff2cfb96 100644
--- a/klippy/pins.py
+++ b/klippy/pins.py
@@ -5,6 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import re
+
class error(Exception):
pass
@@ -13,7 +14,8 @@ class error(Exception):
# Command translation
######################################################################
-re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
+re_pin = re.compile(r"(?P<prefix>[ _]pin=)(?P<name>[^ ]*)")
+
class PinResolver:
def __init__(self, validate_aliases=True):
@@ -21,16 +23,22 @@ class PinResolver:
self.reserved = {}
self.aliases = {}
self.active_pins = {}
+
def reserve_pin(self, pin, reserve_name):
if pin in self.reserved and self.reserved[pin] != reserve_name:
- raise error("Pin %s reserved for %s - can't reserve for %s" % (
- pin, self.reserved[pin], reserve_name))
+ raise error(
+ "Pin %s reserved for %s - can't reserve for %s"
+ % (pin, self.reserved[pin], reserve_name)
+ )
self.reserved[pin] = reserve_name
+
def alias_pin(self, alias, pin):
if alias in self.aliases and self.aliases[alias] != pin:
- raise error("Alias %s mapped to %s - can't alias to %s" % (
- alias, self.aliases[alias], pin))
- if [c for c in '^~!:' if c in pin] or ''.join(pin.split()) != pin:
+ raise error(
+ "Alias %s mapped to %s - can't alias to %s"
+ % (alias, self.aliases[alias], pin)
+ )
+ if [c for c in "^~!:" if c in pin] or "".join(pin.split()) != pin:
raise error("Invalid pin alias '%s'\n" % (pin,))
if pin in self.aliases:
pin = self.aliases[pin]
@@ -38,18 +46,22 @@ class PinResolver:
for existing_alias, existing_pin in self.aliases.items():
if existing_pin == alias:
self.aliases[existing_alias] = pin
+
def update_command(self, cmd):
def pin_fixup(m):
- name = m.group('name')
+ name = m.group("name")
pin_id = self.aliases.get(name, name)
- if (name != self.active_pins.setdefault(pin_id, name)
- and self.validate_aliases):
- raise error("pin %s is an alias for %s" % (
- name, self.active_pins[pin_id]))
+ if (
+ name != self.active_pins.setdefault(pin_id, name)
+ and self.validate_aliases
+ ):
+ raise error(
+ "pin %s is an alias for %s" % (name, self.active_pins[pin_id])
+ )
if pin_id in self.reserved:
- raise error("pin %s is reserved for %s" % (
- name, self.reserved[pin_id]))
- return m.group('prefix') + str(pin_id)
+ raise error("pin %s is reserved for %s" % (name, self.reserved[pin_id]))
+ return m.group("prefix") + str(pin_id)
+
return re_pin.sub(pin_fixup, cmd)
@@ -57,82 +69,99 @@ class PinResolver:
# Pin to chip mapping
######################################################################
+
class PrinterPins:
error = error
+
def __init__(self):
self.chips = {}
self.active_pins = {}
self.pin_resolvers = {}
self.allow_multi_use_pins = {}
+
def parse_pin(self, pin_desc, can_invert=False, can_pullup=False):
desc = pin_desc.strip()
pullup = invert = 0
- if can_pullup and (desc.startswith('^') or desc.startswith('~')):
+ if can_pullup and (desc.startswith("^") or desc.startswith("~")):
pullup = 1
- if desc.startswith('~'):
+ if desc.startswith("~"):
pullup = -1
desc = desc[1:].strip()
- if can_invert and desc.startswith('!'):
+ if can_invert and desc.startswith("!"):
invert = 1
desc = desc[1:].strip()
- if ':' not in desc:
- chip_name, pin = 'mcu', desc
+ if ":" not in desc:
+ chip_name, pin = "mcu", desc
else:
- chip_name, pin = [s.strip() for s in desc.split(':', 1)]
+ chip_name, pin = [s.strip() for s in desc.split(":", 1)]
if chip_name not in self.chips:
raise error("Unknown pin chip name '%s'" % (chip_name,))
- if [c for c in '^~!:' if c in pin] or ''.join(pin.split()) != pin:
+ if [c for c in "^~!:" if c in pin] or "".join(pin.split()) != pin:
format = ""
if can_pullup:
format += "[^~] "
if can_invert:
format += "[!] "
- raise error("Invalid pin description '%s'\n"
- "Format is: %s[chip_name:] pin_name" % (
- pin_desc, format))
- pin_params = {'chip': self.chips[chip_name], 'chip_name': chip_name,
- 'pin': pin, 'invert': invert, 'pullup': pullup}
+ raise error(
+ "Invalid pin description '%s'\n"
+ "Format is: %s[chip_name:] pin_name" % (pin_desc, format)
+ )
+ pin_params = {
+ "chip": self.chips[chip_name],
+ "chip_name": chip_name,
+ "pin": pin,
+ "invert": invert,
+ "pullup": pullup,
+ }
return pin_params
- def lookup_pin(self, pin_desc, can_invert=False, can_pullup=False,
- share_type=None):
+
+ def lookup_pin(self, pin_desc, can_invert=False, can_pullup=False, share_type=None):
pin_params = self.parse_pin(pin_desc, can_invert, can_pullup)
- pin = pin_params['pin']
- share_name = "%s:%s" % (pin_params['chip_name'], pin)
+ pin = pin_params["pin"]
+ share_name = "%s:%s" % (pin_params["chip_name"], pin)
if share_name in self.active_pins:
share_params = self.active_pins[share_name]
if share_name in self.allow_multi_use_pins:
pass
- elif share_type is None or share_type != share_params['share_type']:
+ elif share_type is None or share_type != share_params["share_type"]:
raise error("pin %s used multiple times in config" % (pin,))
- elif (pin_params['invert'] != share_params['invert']
- or pin_params['pullup'] != share_params['pullup']):
+ elif (
+ pin_params["invert"] != share_params["invert"]
+ or pin_params["pullup"] != share_params["pullup"]
+ ):
raise error("Shared pin %s must have same polarity" % (pin,))
return share_params
- pin_params['share_type'] = share_type
+ pin_params["share_type"] = share_type
self.active_pins[share_name] = pin_params
return pin_params
+
def setup_pin(self, pin_type, pin_desc):
- can_invert = pin_type in ['endstop', 'digital_out', 'pwm']
- can_pullup = pin_type in ['endstop']
+ can_invert = pin_type in ["endstop", "digital_out", "pwm"]
+ can_pullup = pin_type in ["endstop"]
pin_params = self.lookup_pin(pin_desc, can_invert, can_pullup)
- return pin_params['chip'].setup_pin(pin_type, pin_params)
+ return pin_params["chip"].setup_pin(pin_type, pin_params)
+
def reset_pin_sharing(self, pin_params):
- share_name = "%s:%s" % (pin_params['chip_name'], pin_params['pin'])
+ share_name = "%s:%s" % (pin_params["chip_name"], pin_params["pin"])
del self.active_pins[share_name]
+
def get_pin_resolver(self, chip_name):
if chip_name not in self.pin_resolvers:
raise error("Unknown chip name '%s'" % (chip_name,))
return self.pin_resolvers[chip_name]
+
def register_chip(self, chip_name, chip):
chip_name = chip_name.strip()
if chip_name in self.chips:
raise error("Duplicate chip name '%s'" % (chip_name,))
self.chips[chip_name] = chip
self.pin_resolvers[chip_name] = PinResolver()
+
def allow_multi_use_pin(self, pin_desc):
pin_params = self.parse_pin(pin_desc)
- share_name = "%s:%s" % (pin_params['chip_name'], pin_params['pin'])
+ share_name = "%s:%s" % (pin_params["chip_name"], pin_params["pin"])
self.allow_multi_use_pins[share_name] = True
+
def add_printer_objects(config):
- config.get_printer().add_object('pins', PrinterPins())
+ config.get_printer().add_object("pins", PrinterPins())
diff --git a/klippy/queuelogger.py b/klippy/queuelogger.py
index c6447f8e..b5bfc612 100644
--- a/klippy/queuelogger.py
+++ b/klippy/queuelogger.py
@@ -5,11 +5,13 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, logging.handlers, threading, queue, time
+
# Class to forward all messages through a queue to a background thread
class QueueHandler(logging.Handler):
def __init__(self, queue):
logging.Handler.__init__(self)
self.queue = queue
+
def emit(self, record):
try:
self.format(record)
@@ -20,43 +22,52 @@ class QueueHandler(logging.Handler):
except Exception:
self.handleError(record)
+
# Class to poll a queue in a background thread and log each message
class QueueListener(logging.handlers.TimedRotatingFileHandler):
def __init__(self, filename):
logging.handlers.TimedRotatingFileHandler.__init__(
- self, filename, when='midnight', backupCount=5)
+ self, filename, when="midnight", backupCount=5
+ )
self.bg_queue = queue.Queue()
self.bg_thread = threading.Thread(target=self._bg_thread)
self.bg_thread.start()
self.rollover_info = {}
+
def _bg_thread(self):
while 1:
record = self.bg_queue.get(True)
if record is None:
break
self.handle(record)
+
def stop(self):
self.bg_queue.put_nowait(None)
self.bg_thread.join()
+
def set_rollover_info(self, name, info):
if info is None:
self.rollover_info.pop(name, None)
return
self.rollover_info[name] = info
+
def clear_rollover_info(self):
self.rollover_info.clear()
+
def doRollover(self):
logging.handlers.TimedRotatingFileHandler.doRollover(self)
- lines = [self.rollover_info[name]
- for name in sorted(self.rollover_info)]
+ lines = [self.rollover_info[name] for name in sorted(self.rollover_info)]
lines.append(
- "=============== Log rollover at %s ===============" % (
- time.asctime(),))
- self.emit(logging.makeLogRecord(
- {'msg': "\n".join(lines), 'level': logging.INFO}))
+ "=============== Log rollover at %s ===============" % (time.asctime(),)
+ )
+ self.emit(
+ logging.makeLogRecord({"msg": "\n".join(lines), "level": logging.INFO})
+ )
+
MainQueueHandler = None
+
def setup_bg_logging(filename, debuglevel):
global MainQueueHandler
ql = QueueListener(filename)
@@ -66,6 +77,7 @@ def setup_bg_logging(filename, debuglevel):
root.setLevel(debuglevel)
return ql
+
def clear_bg_logging():
global MainQueueHandler
if MainQueueHandler is not None:
diff --git a/klippy/reactor.py b/klippy/reactor.py
index 412d53ed..c5979b40 100644
--- a/klippy/reactor.py
+++ b/klippy/reactor.py
@@ -7,26 +7,33 @@ import os, gc, select, math, time, logging, queue
import greenlet
import chelper, util
-_NOW = 0.
-_NEVER = 9999999999999999.
+_NOW = 0.0
+_NEVER = 9999999999999999.0
+
class ReactorTimer:
def __init__(self, callback, waketime):
self.callback = callback
self.waketime = waketime
+
class ReactorCompletion:
- class sentinel: pass
+ class sentinel:
+ pass
+
def __init__(self, reactor):
self.reactor = reactor
self.result = self.sentinel
self.waiting = []
+
def test(self):
return self.result is not self.sentinel
+
def complete(self, result):
self.result = result
for wait in self.waiting:
self.reactor.update_timer(wait.timer, self.reactor.NOW)
+
def wait(self, waketime=_NEVER, waketime_result=None):
if self.result is self.sentinel:
wait = greenlet.getcurrent()
@@ -37,31 +44,37 @@ class ReactorCompletion:
return waketime_result
return self.result
+
class ReactorCallback:
def __init__(self, reactor, callback, waketime):
self.reactor = reactor
self.timer = reactor.register_timer(self.invoke, waketime)
self.callback = callback
self.completion = ReactorCompletion(reactor)
+
def invoke(self, eventtime):
self.reactor.unregister_timer(self.timer)
res = self.callback(eventtime)
self.completion.complete(res)
return self.reactor.NEVER
+
class ReactorFileHandler:
def __init__(self, fd, read_callback, write_callback):
self.fd = fd
self.read_callback = read_callback
self.write_callback = write_callback
+
def fileno(self):
return self.fd
+
class ReactorGreenlet(greenlet.greenlet):
def __init__(self, run):
greenlet.greenlet.__init__(self, run=run)
self.timer = None
+
class ReactorMutex:
def __init__(self, reactor, is_locked):
self.reactor = reactor
@@ -70,8 +83,10 @@ class ReactorMutex:
self.queue = []
self.lock = self.__enter__
self.unlock = self.__exit__
+
def test(self):
return self.is_locked
+
def __enter__(self):
if not self.is_locked:
self.is_locked = True
@@ -84,6 +99,7 @@ class ReactorMutex:
self.next_pending = False
self.queue.pop(0)
return
+
def __exit__(self, type=None, value=None, tb=None):
if not self.queue:
self.is_locked = False
@@ -91,16 +107,18 @@ class ReactorMutex:
self.next_pending = True
self.reactor.update_timer(self.queue[0].timer, self.reactor.NOW)
+
class SelectReactor:
NOW = _NOW
NEVER = _NEVER
+
def __init__(self, gc_checking=False):
# Main code
self._process = False
self.monotonic = chelper.get_ffi()[1].get_monotonic
# Python garbage collection
self._check_gc = gc_checking
- self._last_gc_times = [0., 0., 0.]
+ self._last_gc_times = [0.0, 0.0, 0.0]
# Timers
self._timers = []
self._next_timer = self.NEVER
@@ -114,12 +132,15 @@ class SelectReactor:
self._g_dispatch = None
self._greenlets = []
self._all_greenlets = []
+
def get_gc_stats(self):
return tuple(self._last_gc_times)
+
# Timers
def update_timer(self, timer_handler, waketime):
timer_handler.waketime = waketime
self._next_timer = min(self._next_timer, waketime)
+
def register_timer(self, callback, waketime=NEVER):
timer_handler = ReactorTimer(callback, waketime)
timers = list(self._timers)
@@ -127,15 +148,17 @@ class SelectReactor:
self._timers = timers
self._next_timer = min(self._next_timer, waketime)
return timer_handler
+
def unregister_timer(self, timer_handler):
timer_handler.waketime = self.NEVER
timers = list(self._timers)
timers.pop(timers.index(timer_handler))
self._timers = timers
+
def _check_timers(self, eventtime, busy):
if eventtime < self._next_timer:
if busy:
- return 0.
+ return 0.0
if self._check_gc:
gi = gc.get_count()
if gi[0] >= 700:
@@ -147,8 +170,8 @@ class SelectReactor:
gc_level = 2
self._last_gc_times[gc_level] = eventtime
gc.collect(gc_level)
- return 0.
- return min(1., max(.001, self._next_timer - eventtime))
+ return 0.0
+ return min(1.0, max(0.001, self._next_timer - eventtime))
self._next_timer = self.NEVER
g_dispatch = self._g_dispatch
for t in self._timers:
@@ -159,29 +182,33 @@ class SelectReactor:
if g_dispatch is not self._g_dispatch:
self._next_timer = min(self._next_timer, waketime)
self._end_greenlet(g_dispatch)
- return 0.
+ return 0.0
self._next_timer = min(self._next_timer, waketime)
- return 0.
+ return 0.0
+
# Callbacks and Completions
def completion(self):
return ReactorCompletion(self)
+
def register_callback(self, callback, waketime=NOW):
rcb = ReactorCallback(self, callback, waketime)
return rcb.completion
+
# Asynchronous (from another thread) callbacks and completions
def register_async_callback(self, callback, waketime=NOW):
- self._async_queue.put_nowait(
- (ReactorCallback, (self, callback, waketime)))
+ self._async_queue.put_nowait((ReactorCallback, (self, callback, waketime)))
try:
- os.write(self._pipe_fds[1], b'.')
+ os.write(self._pipe_fds[1], b".")
except os.error:
pass
+
def async_complete(self, completion, result):
self._async_queue.put_nowait((completion.complete, (result,)))
try:
- os.write(self._pipe_fds[1], b'.')
+ os.write(self._pipe_fds[1], b".")
except os.error:
pass
+
def _got_pipe_signal(self, eventtime):
try:
os.read(self._pipe_fds[0], 4096)
@@ -193,18 +220,21 @@ class SelectReactor:
except queue.Empty:
break
func(*args)
+
def _setup_async_callbacks(self):
self._pipe_fds = os.pipe()
util.set_nonblock(self._pipe_fds[0])
util.set_nonblock(self._pipe_fds[1])
self.register_fd(self._pipe_fds[0], self._got_pipe_signal)
+
# Greenlets
def _sys_pause(self, waketime):
# Pause using system sleep for when reactor not running
delay = waketime - self.monotonic()
- if delay > 0.:
+ if delay > 0.0:
time.sleep(delay)
return self.monotonic()
+
def pause(self, waketime):
g = greenlet.getcurrent()
if g is not self._g_dispatch:
@@ -225,6 +255,7 @@ class SelectReactor:
eventtime = g_next.switch()
# This greenlet activated from g.timer.callback (via _check_timers)
return eventtime
+
def _end_greenlet(self, g_old):
# Cache this greenlet for later use
self._greenlets.append(g_old)
@@ -234,19 +265,23 @@ class SelectReactor:
self._g_dispatch.switch(self.NEVER)
# This greenlet reactivated from pause() - return to main dispatch loop
self._g_dispatch = g_old
+
# Mutexes
def mutex(self, is_locked=False):
return ReactorMutex(self, is_locked)
+
# File descriptors
def register_fd(self, fd, read_callback, write_callback=None):
file_handler = ReactorFileHandler(fd, read_callback, write_callback)
self.set_fd_wake(file_handler, True, False)
return file_handler
+
def unregister_fd(self, file_handler):
if file_handler in self._read_fds:
self._read_fds.pop(self._read_fds.index(file_handler))
if file_handler in self._write_fds:
self._write_fds.pop(self._write_fds.index(file_handler))
+
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
if file_handler in self._read_fds:
if not is_readable:
@@ -258,6 +293,7 @@ class SelectReactor:
self._write_fds.pop(self._write_fds.index(file_handler))
elif is_writeable:
self._write_fds.append(file_handler)
+
# Main loop
def _dispatch_loop(self):
self._g_dispatch = g_dispatch = greenlet.getcurrent()
@@ -283,6 +319,7 @@ class SelectReactor:
eventtime = self.monotonic()
break
self._g_dispatch = None
+
def run(self):
if self._pipe_fds is None:
self._setup_async_callbacks()
@@ -290,8 +327,10 @@ class SelectReactor:
g_next = ReactorGreenlet(run=self._dispatch_loop)
self._all_greenlets.append(g_next)
g_next.switch()
+
def end(self):
self._process = False
+
def finalize(self):
self._g_dispatch = None
self._greenlets = []
@@ -306,11 +345,13 @@ class SelectReactor:
os.close(self._pipe_fds[1])
self._pipe_fds = None
+
class PollReactor(SelectReactor):
def __init__(self, gc_checking=False):
SelectReactor.__init__(self, gc_checking)
self._poll = select.poll()
self._fds = {}
+
# File descriptors
def register_fd(self, fd, read_callback, write_callback=None):
file_handler = ReactorFileHandler(fd, read_callback, write_callback)
@@ -319,11 +360,13 @@ class PollReactor(SelectReactor):
self._fds = fds
self._poll.register(file_handler, select.POLLIN | select.POLLHUP)
return file_handler
+
def unregister_fd(self, file_handler):
self._poll.unregister(file_handler)
fds = self._fds.copy()
del fds[file_handler.fd]
self._fds = fds
+
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
flags = select.POLLHUP
if is_readable:
@@ -331,6 +374,7 @@ class PollReactor(SelectReactor):
if is_writeable:
flags |= select.POLLOUT
self._poll.modify(file_handler, flags)
+
# Main loop
def _dispatch_loop(self):
self._g_dispatch = g_dispatch = greenlet.getcurrent()
@@ -339,7 +383,7 @@ class PollReactor(SelectReactor):
while self._process:
timeout = self._check_timers(eventtime, busy)
busy = False
- res = self._poll.poll(int(math.ceil(timeout * 1000.)))
+ res = self._poll.poll(int(math.ceil(timeout * 1000.0)))
eventtime = self.monotonic()
for fd, event in res:
busy = True
@@ -357,11 +401,13 @@ class PollReactor(SelectReactor):
break
self._g_dispatch = None
+
class EPollReactor(SelectReactor):
def __init__(self, gc_checking=False):
SelectReactor.__init__(self, gc_checking)
self._epoll = select.epoll()
self._fds = {}
+
# File descriptors
def register_fd(self, fd, read_callback, write_callback=None):
file_handler = ReactorFileHandler(fd, read_callback, write_callback)
@@ -370,11 +416,13 @@ class EPollReactor(SelectReactor):
self._fds = fds
self._epoll.register(fd, select.EPOLLIN | select.EPOLLHUP)
return file_handler
+
def unregister_fd(self, file_handler):
self._epoll.unregister(file_handler.fd)
fds = self._fds.copy()
del fds[file_handler.fd]
self._fds = fds
+
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
flags = select.POLLHUP
if is_readable:
@@ -382,6 +430,7 @@ class EPollReactor(SelectReactor):
if is_writeable:
flags |= select.EPOLLOUT
self._epoll.modify(file_handler, flags)
+
# Main loop
def _dispatch_loop(self):
self._g_dispatch = g_dispatch = greenlet.getcurrent()
@@ -408,6 +457,7 @@ class EPollReactor(SelectReactor):
break
self._g_dispatch = None
+
# Use the poll based reactor if it is available
try:
select.poll
diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py
index fc884638..97169373 100644
--- a/klippy/serialhdl.py
+++ b/klippy/serialhdl.py
@@ -8,9 +8,11 @@ import serial
import msgproto, chelper, util
+
class error(Exception):
pass
+
class SerialReader:
def __init__(self, reactor, warn_prefix=""):
self.reactor = reactor
@@ -22,71 +24,77 @@ class SerialReader:
self.ffi_main, self.ffi_lib = chelper.get_ffi()
self.serialqueue = None
self.default_cmd_queue = self.alloc_command_queue()
- self.stats_buf = self.ffi_main.new('char[4096]')
+ self.stats_buf = self.ffi_main.new("char[4096]")
# Threading
self.lock = threading.Lock()
self.background_thread = None
# Message handlers
self.handlers = {}
- self.register_response(self._handle_unknown_init, '#unknown')
- self.register_response(self.handle_output, '#output')
+ self.register_response(self._handle_unknown_init, "#unknown")
+ self.register_response(self.handle_output, "#output")
# Sent message notification tracking
self.last_notify_id = 0
self.pending_notifications = {}
+
def _bg_thread(self):
- response = self.ffi_main.new('struct pull_queue_message *')
+ response = self.ffi_main.new("struct pull_queue_message *")
while 1:
self.ffi_lib.serialqueue_pull(self.serialqueue, response)
count = response.len
if count < 0:
break
if response.notify_id:
- params = {'#sent_time': response.sent_time,
- '#receive_time': response.receive_time}
+ params = {
+ "#sent_time": response.sent_time,
+ "#receive_time": response.receive_time,
+ }
completion = self.pending_notifications.pop(response.notify_id)
self.reactor.async_complete(completion, params)
continue
params = self.msgparser.parse(response.msg[0:count])
- params['#sent_time'] = response.sent_time
- params['#receive_time'] = response.receive_time
- hdl = (params['#name'], params.get('oid'))
+ params["#sent_time"] = response.sent_time
+ params["#receive_time"] = response.receive_time
+ hdl = (params["#name"], params.get("oid"))
try:
with self.lock:
hdl = self.handlers.get(hdl, self.handle_default)
hdl(params)
except:
- logging.exception("%sException in serial callback",
- self.warn_prefix)
+ logging.exception("%sException in serial callback", self.warn_prefix)
+
def _error(self, msg, *params):
raise error(self.warn_prefix + (msg % params))
+
def _get_identify_data(self, eventtime):
# Query the "data dictionary" from the micro-controller
identify_data = b""
while 1:
msg = "identify offset=%d count=%d" % (len(identify_data), 40)
try:
- params = self.send_with_response(msg, 'identify_response')
+ params = self.send_with_response(msg, "identify_response")
except error as e:
- logging.exception("%sWait for identify_response",
- self.warn_prefix)
+ logging.exception("%sWait for identify_response", self.warn_prefix)
return None
- if params['offset'] == len(identify_data):
- msgdata = params['data']
+ if params["offset"] == len(identify_data):
+ msgdata = params["data"]
if not msgdata:
# Done
return identify_data
identify_data += msgdata
- def _start_session(self, serial_dev, serial_fd_type=b'u', client_id=0):
+
+ def _start_session(self, serial_dev, serial_fd_type=b"u", client_id=0):
self.serial_dev = serial_dev
self.serialqueue = self.ffi_main.gc(
- self.ffi_lib.serialqueue_alloc(serial_dev.fileno(),
- serial_fd_type, client_id),
- self.ffi_lib.serialqueue_free)
+ self.ffi_lib.serialqueue_alloc(
+ serial_dev.fileno(), serial_fd_type, client_id
+ ),
+ self.ffi_lib.serialqueue_free,
+ )
self.background_thread = threading.Thread(target=self._bg_thread)
self.background_thread.start()
# Obtain and load the data dictionary from the firmware
completion = self.reactor.register_callback(self._get_identify_data)
- identify_data = completion.wait(self.reactor.monotonic() + 5.)
+ identify_data = completion.wait(self.reactor.monotonic() + 5.0)
if identify_data is None:
logging.info("%sTimeout on connect", self.warn_prefix)
self.disconnect()
@@ -94,117 +102,121 @@ class SerialReader:
msgparser = msgproto.MessageParser(warn_prefix=self.warn_prefix)
msgparser.process_identify(identify_data)
self.msgparser = msgparser
- self.register_response(self.handle_unknown, '#unknown')
+ self.register_response(self.handle_unknown, "#unknown")
# Setup baud adjust
- if serial_fd_type == b'c':
- wire_freq = msgparser.get_constant_float('CANBUS_FREQUENCY', None)
+ if serial_fd_type == b"c":
+ wire_freq = msgparser.get_constant_float("CANBUS_FREQUENCY", None)
else:
- wire_freq = msgparser.get_constant_float('SERIAL_BAUD', None)
+ wire_freq = msgparser.get_constant_float("SERIAL_BAUD", None)
if wire_freq is not None:
- self.ffi_lib.serialqueue_set_wire_frequency(self.serialqueue,
- wire_freq)
- receive_window = msgparser.get_constant_int('RECEIVE_WINDOW', None)
+ self.ffi_lib.serialqueue_set_wire_frequency(self.serialqueue, wire_freq)
+ receive_window = msgparser.get_constant_int("RECEIVE_WINDOW", None)
if receive_window is not None:
self.ffi_lib.serialqueue_set_receive_window(
- self.serialqueue, receive_window)
+ self.serialqueue, receive_window
+ )
return True
+
def connect_canbus(self, canbus_uuid, canbus_nodeid, canbus_iface="can0"):
- import can # XXX
+ import can # XXX
+
txid = canbus_nodeid * 2 + 256
- filters = [{"can_id": txid+1, "can_mask": 0x7ff, "extended": False}]
+ filters = [{"can_id": txid + 1, "can_mask": 0x7FF, "extended": False}]
# Prep for SET_NODEID command
try:
uuid = int(canbus_uuid, 16)
except ValueError:
uuid = -1
- if uuid < 0 or uuid > 0xffffffffffff:
+ if uuid < 0 or uuid > 0xFFFFFFFFFFFF:
self._error("Invalid CAN uuid")
- uuid = [(uuid >> (40 - i*8)) & 0xff for i in range(6)]
- CANBUS_ID_ADMIN = 0x3f0
+ uuid = [(uuid >> (40 - i * 8)) & 0xFF for i in range(6)]
+ CANBUS_ID_ADMIN = 0x3F0
CMD_SET_NODEID = 0x01
set_id_cmd = [CMD_SET_NODEID] + uuid + [canbus_nodeid]
- set_id_msg = can.Message(arbitration_id=CANBUS_ID_ADMIN,
- data=set_id_cmd, is_extended_id=False)
+ set_id_msg = can.Message(
+ arbitration_id=CANBUS_ID_ADMIN, data=set_id_cmd, is_extended_id=False
+ )
# Start connection attempt
logging.info("%sStarting CAN connect", self.warn_prefix)
start_time = self.reactor.monotonic()
while 1:
- if self.reactor.monotonic() > start_time + 90.:
+ if self.reactor.monotonic() > start_time + 90.0:
self._error("Unable to connect")
try:
- bus = can.interface.Bus(channel=canbus_iface,
- can_filters=filters,
- bustype='socketcan')
+ bus = can.interface.Bus(
+ channel=canbus_iface, can_filters=filters, bustype="socketcan"
+ )
bus.send(set_id_msg)
except (can.CanError, os.error, IOError) as e:
- logging.warning("%sUnable to open CAN port: %s",
- self.warn_prefix, e)
- self.reactor.pause(self.reactor.monotonic() + 5.)
+ logging.warning("%sUnable to open CAN port: %s", self.warn_prefix, e)
+ self.reactor.pause(self.reactor.monotonic() + 5.0)
continue
- bus.close = bus.shutdown # XXX
- ret = self._start_session(bus, b'c', txid)
+ bus.close = bus.shutdown # XXX
+ ret = self._start_session(bus, b"c", txid)
if not ret:
continue
# Verify correct canbus_nodeid to canbus_uuid mapping
try:
- params = self.send_with_response('get_canbus_id', 'canbus_id')
- got_uuid = bytearray(params['canbus_uuid'])
+ params = self.send_with_response("get_canbus_id", "canbus_id")
+ got_uuid = bytearray(params["canbus_uuid"])
if got_uuid == bytearray(uuid):
break
except:
- logging.exception("%sError in canbus_uuid check",
- self.warn_prefix)
- logging.info("%sFailed to match canbus_uuid - retrying..",
- self.warn_prefix)
+ logging.exception("%sError in canbus_uuid check", self.warn_prefix)
+ logging.info("%sFailed to match canbus_uuid - retrying..", self.warn_prefix)
self.disconnect()
+
def connect_pipe(self, filename):
logging.info("%sStarting connect", self.warn_prefix)
start_time = self.reactor.monotonic()
while 1:
- if self.reactor.monotonic() > start_time + 90.:
+ if self.reactor.monotonic() > start_time + 90.0:
self._error("Unable to connect")
try:
fd = os.open(filename, os.O_RDWR | os.O_NOCTTY)
except OSError as e:
- logging.warning("%sUnable to open port: %s",
- self.warn_prefix, e)
- self.reactor.pause(self.reactor.monotonic() + 5.)
+ logging.warning("%sUnable to open port: %s", self.warn_prefix, e)
+ self.reactor.pause(self.reactor.monotonic() + 5.0)
continue
- serial_dev = os.fdopen(fd, 'rb+', 0)
+ serial_dev = os.fdopen(fd, "rb+", 0)
ret = self._start_session(serial_dev)
if ret:
break
+
def connect_uart(self, serialport, baud, rts=True):
# Initial connection
logging.info("%sStarting serial connect", self.warn_prefix)
start_time = self.reactor.monotonic()
while 1:
- if self.reactor.monotonic() > start_time + 90.:
+ if self.reactor.monotonic() > start_time + 90.0:
self._error("Unable to connect")
try:
- serial_dev = serial.Serial(baudrate=baud, timeout=0,
- exclusive=True)
+ serial_dev = serial.Serial(baudrate=baud, timeout=0, exclusive=True)
serial_dev.port = serialport
serial_dev.rts = rts
serial_dev.open()
except (OSError, IOError, serial.SerialException) as e:
- logging.warning("%sUnable to open serial port: %s",
- self.warn_prefix, e)
- self.reactor.pause(self.reactor.monotonic() + 5.)
+ logging.warning("%sUnable to open serial port: %s", self.warn_prefix, e)
+ self.reactor.pause(self.reactor.monotonic() + 5.0)
continue
stk500v2_leave(serial_dev, self.reactor)
ret = self._start_session(serial_dev)
if ret:
break
+
def connect_file(self, debugoutput, dictionary, pace=False):
self.serial_dev = debugoutput
self.msgparser.process_identify(dictionary, decompress=False)
self.serialqueue = self.ffi_main.gc(
- self.ffi_lib.serialqueue_alloc(self.serial_dev.fileno(), b'f', 0),
- self.ffi_lib.serialqueue_free)
+ self.ffi_lib.serialqueue_alloc(self.serial_dev.fileno(), b"f", 0),
+ self.ffi_lib.serialqueue_free,
+ )
+
def set_clock_est(self, freq, conv_time, conv_clock, last_clock):
self.ffi_lib.serialqueue_set_clock_est(
- self.serialqueue, freq, conv_time, conv_clock, last_clock)
+ self.serialqueue, freq, conv_time, conv_clock, last_clock
+ )
+
def disconnect(self):
if self.serialqueue is not None:
self.ffi_lib.serialqueue_exit(self.serialqueue)
@@ -217,20 +229,27 @@ class SerialReader:
for pn in self.pending_notifications.values():
pn.complete(None)
self.pending_notifications.clear()
+
def stats(self, eventtime):
if self.serialqueue is None:
return ""
- self.ffi_lib.serialqueue_get_stats(self.serialqueue,
- self.stats_buf, len(self.stats_buf))
+ self.ffi_lib.serialqueue_get_stats(
+ self.serialqueue, self.stats_buf, len(self.stats_buf)
+ )
return str(self.ffi_main.string(self.stats_buf).decode())
+
def get_reactor(self):
return self.reactor
+
def get_msgparser(self):
return self.msgparser
+
def get_serialqueue(self):
return self.serialqueue
+
def get_default_command_queue(self):
return self.default_cmd_queue
+
# Serial response callbacks
def register_response(self, callback, name, oid=None):
with self.lock:
@@ -238,68 +257,95 @@ class SerialReader:
del self.handlers[name, oid]
else:
self.handlers[name, oid] = callback
+
# Command sending
def raw_send(self, cmd, minclock, reqclock, cmd_queue):
- self.ffi_lib.serialqueue_send(self.serialqueue, cmd_queue,
- cmd, len(cmd), minclock, reqclock, 0)
+ self.ffi_lib.serialqueue_send(
+ self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock, 0
+ )
+
def raw_send_wait_ack(self, cmd, minclock, reqclock, cmd_queue):
self.last_notify_id += 1
nid = self.last_notify_id
completion = self.reactor.completion()
self.pending_notifications[nid] = completion
- self.ffi_lib.serialqueue_send(self.serialqueue, cmd_queue,
- cmd, len(cmd), minclock, reqclock, nid)
+ self.ffi_lib.serialqueue_send(
+ self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock, nid
+ )
params = completion.wait()
if params is None:
self._error("Serial connection closed")
return params
+
def send(self, msg, minclock=0, reqclock=0):
cmd = self.msgparser.create_command(msg)
self.raw_send(cmd, minclock, reqclock, self.default_cmd_queue)
+
def send_with_response(self, msg, response):
cmd = self.msgparser.create_command(msg)
src = SerialRetryCommand(self, response)
return src.get_response([cmd], self.default_cmd_queue)
+
def alloc_command_queue(self):
- return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
- self.ffi_lib.serialqueue_free_commandqueue)
+ return self.ffi_main.gc(
+ self.ffi_lib.serialqueue_alloc_commandqueue(),
+ self.ffi_lib.serialqueue_free_commandqueue,
+ )
+
# Dumping debug lists
def dump_debug(self):
out = []
- out.append("Dumping serial stats: %s" % (
- self.stats(self.reactor.monotonic()),))
- sdata = self.ffi_main.new('struct pull_queue_message[1024]')
- rdata = self.ffi_main.new('struct pull_queue_message[1024]')
- scount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 1,
- sdata, len(sdata))
- rcount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 0,
- rdata, len(rdata))
+ out.append("Dumping serial stats: %s" % (self.stats(self.reactor.monotonic()),))
+ sdata = self.ffi_main.new("struct pull_queue_message[1024]")
+ rdata = self.ffi_main.new("struct pull_queue_message[1024]")
+ scount = self.ffi_lib.serialqueue_extract_old(
+ self.serialqueue, 1, sdata, len(sdata)
+ )
+ rcount = self.ffi_lib.serialqueue_extract_old(
+ self.serialqueue, 0, rdata, len(rdata)
+ )
out.append("Dumping send queue %d messages" % (scount,))
for i in range(scount):
msg = sdata[i]
- cmds = self.msgparser.dump(msg.msg[0:msg.len])
- out.append("Sent %d %f %f %d: %s" % (
- i, msg.receive_time, msg.sent_time, msg.len, ', '.join(cmds)))
+ cmds = self.msgparser.dump(msg.msg[0 : msg.len])
+ out.append(
+ "Sent %d %f %f %d: %s"
+ % (i, msg.receive_time, msg.sent_time, msg.len, ", ".join(cmds))
+ )
out.append("Dumping receive queue %d messages" % (rcount,))
for i in range(rcount):
msg = rdata[i]
- cmds = self.msgparser.dump(msg.msg[0:msg.len])
- out.append("Receive: %d %f %f %d: %s" % (
- i, msg.receive_time, msg.sent_time, msg.len, ', '.join(cmds)))
- return '\n'.join(out)
+ cmds = self.msgparser.dump(msg.msg[0 : msg.len])
+ out.append(
+ "Receive: %d %f %f %d: %s"
+ % (i, msg.receive_time, msg.sent_time, msg.len, ", ".join(cmds))
+ )
+ return "\n".join(out)
+
# Default message handlers
def _handle_unknown_init(self, params):
- logging.debug("%sUnknown message %d (len %d) while identifying",
- self.warn_prefix, params['#msgid'], len(params['#msg']))
+ logging.debug(
+ "%sUnknown message %d (len %d) while identifying",
+ self.warn_prefix,
+ params["#msgid"],
+ len(params["#msg"]),
+ )
+
def handle_unknown(self, params):
- logging.warning("%sUnknown message type %d: %s",
- self.warn_prefix, params['#msgid'], repr(params['#msg']))
+ logging.warning(
+ "%sUnknown message type %d: %s",
+ self.warn_prefix,
+ params["#msgid"],
+ repr(params["#msg"]),
+ )
+
def handle_output(self, params):
- logging.info("%s%s: %s", self.warn_prefix,
- params['#name'], params['#msg'])
+ logging.info("%s%s: %s", self.warn_prefix, params["#name"], params["#msg"])
+
def handle_default(self, params):
logging.warning("%sgot %s", self.warn_prefix, params)
+
# Class to send a query command and return the received response
class SerialRetryCommand:
def __init__(self, serial, name, oid=None):
@@ -308,19 +354,19 @@ class SerialRetryCommand:
self.oid = oid
self.last_params = None
self.serial.register_response(self.handle_callback, name, oid)
+
def handle_callback(self, params):
self.last_params = params
- def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0,
- retry=True):
+
+ def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0, retry=True):
retries = 5
- retry_delay = .010
+ retry_delay = 0.010
if not retry:
retries = 0
while 1:
for cmd in cmds[:-1]:
self.serial.raw_send(cmd, minclock, reqclock, cmd_queue)
- self.serial.raw_send_wait_ack(cmds[-1], minclock, reqclock,
- cmd_queue)
+ self.serial.raw_send_wait_ack(cmds[-1], minclock, reqclock, cmd_queue)
params = self.last_params
if params is not None:
self.serial.register_response(None, self.name, self.oid)
@@ -331,7 +377,8 @@ class SerialRetryCommand:
reactor = self.serial.reactor
reactor.pause(reactor.monotonic() + retry_delay)
retries -= 1
- retry_delay *= 2.
+ retry_delay *= 2.0
+
# Attempt to place an AVR stk500v2 style programmer into normal mode
def stk500v2_leave(ser, reactor):
@@ -345,12 +392,13 @@ def stk500v2_leave(ser, reactor):
ser.baudrate = 115200
reactor.pause(reactor.monotonic() + 0.100)
ser.read(4096)
- ser.write(b'\x1b\x01\x00\x01\x0e\x11\x04')
+ ser.write(b"\x1b\x01\x00\x01\x0e\x11\x04")
reactor.pause(reactor.monotonic() + 0.050)
res = ser.read(4096)
logging.debug("Got %s from stk500v2", repr(res))
ser.baudrate = origbaud
+
def cheetah_reset(serialport, reactor):
# Fysetc Cheetah v1.2 boards have a weird stateful circuitry for
# configuring the bootloader. This sequence takes care of disabling it for
@@ -377,6 +425,7 @@ def cheetah_reset(serialport, reactor):
reactor.pause(reactor.monotonic() + 0.100)
ser.close()
+
# Attempt an arduino style reset on a serial port
def arduino_reset(serialport, reactor):
# First try opening the port at a different baud
diff --git a/klippy/stepper.py b/klippy/stepper.py
index 86a92358..09b1b9e1 100644
--- a/klippy/stepper.py
+++ b/klippy/stepper.py
@@ -6,6 +6,7 @@
import math, logging, collections
import chelper
+
class error(Exception):
pass
@@ -17,78 +18,96 @@ class error(Exception):
MIN_BOTH_EDGE_DURATION = 0.000000500
MIN_OPTIMIZED_BOTH_EDGE_DURATION = 0.000000150
+
# Interface to low-level mcu and chelper code
class MCU_stepper:
- def __init__(self, name, step_pin_params, dir_pin_params,
- rotation_dist, steps_per_rotation,
- step_pulse_duration=None, units_in_radians=False):
+ def __init__(
+ self,
+ name,
+ step_pin_params,
+ dir_pin_params,
+ rotation_dist,
+ steps_per_rotation,
+ step_pulse_duration=None,
+ units_in_radians=False,
+ ):
self._name = name
self._rotation_dist = rotation_dist
self._steps_per_rotation = steps_per_rotation
self._step_pulse_duration = step_pulse_duration
self._units_in_radians = units_in_radians
self._step_dist = rotation_dist / steps_per_rotation
- self._mcu = step_pin_params['chip']
+ self._mcu = step_pin_params["chip"]
self._oid = oid = self._mcu.create_oid()
self._mcu.register_config_callback(self._build_config)
- self._step_pin = step_pin_params['pin']
- self._invert_step = step_pin_params['invert']
- if dir_pin_params['chip'] is not self._mcu:
+ self._step_pin = step_pin_params["pin"]
+ self._invert_step = step_pin_params["invert"]
+ if dir_pin_params["chip"] is not self._mcu:
raise self._mcu.get_printer().config_error(
- "Stepper dir pin must be on same mcu as step pin")
- self._dir_pin = dir_pin_params['pin']
- self._invert_dir = self._orig_invert_dir = dir_pin_params['invert']
+ "Stepper dir pin must be on same mcu as step pin"
+ )
+ self._dir_pin = dir_pin_params["pin"]
+ self._invert_dir = self._orig_invert_dir = dir_pin_params["invert"]
self._step_both_edge = self._req_step_both_edge = False
- self._mcu_position_offset = 0.
+ self._mcu_position_offset = 0.0
self._reset_cmd_tag = self._get_position_cmd = None
self._active_callbacks = []
ffi_main, ffi_lib = chelper.get_ffi()
- self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(oid),
- ffi_lib.stepcompress_free)
+ self._stepqueue = ffi_main.gc(
+ ffi_lib.stepcompress_alloc(oid), ffi_lib.stepcompress_free
+ )
ffi_lib.stepcompress_set_invert_sdir(self._stepqueue, self._invert_dir)
self._mcu.register_stepqueue(self._stepqueue)
self._stepper_kinematics = None
self._itersolve_generate_steps = ffi_lib.itersolve_generate_steps
self._itersolve_check_active = ffi_lib.itersolve_check_active
self._trapq = ffi_main.NULL
- self._mcu.get_printer().register_event_handler('klippy:connect',
- self._query_mcu_position)
+ self._mcu.get_printer().register_event_handler(
+ "klippy:connect", self._query_mcu_position
+ )
+
def get_mcu(self):
return self._mcu
+
def get_name(self, short=False):
- if short and self._name.startswith('stepper'):
+ if short and self._name.startswith("stepper"):
# Skip an extra symbol after 'stepper'
return self._name[8:]
return self._name
+
def units_in_radians(self):
# Returns true if distances are in radians instead of millimeters
return self._units_in_radians
+
def get_pulse_duration(self):
return self._step_pulse_duration, self._step_both_edge
+
def setup_default_pulse_duration(self, pulse_duration, step_both_edge):
if self._step_pulse_duration is None:
self._step_pulse_duration = pulse_duration
self._req_step_both_edge = step_both_edge
+
def setup_itersolve(self, alloc_func, *params):
ffi_main, ffi_lib = chelper.get_ffi()
sk = ffi_main.gc(getattr(ffi_lib, alloc_func)(*params), ffi_lib.free)
self.set_stepper_kinematics(sk)
+
def _build_config(self):
if self._step_pulse_duration is None:
- self._step_pulse_duration = .000002
+ self._step_pulse_duration = 0.000002
invert_step = self._invert_step
# Check if can enable "step on both edges"
constants = self._mcu.get_constants()
- ssbe = int(constants.get('STEPPER_STEP_BOTH_EDGE', '0'))
- sbe = int(constants.get('STEPPER_BOTH_EDGE', '0'))
- sou = int(constants.get('STEPPER_OPTIMIZED_UNSTEP', '0'))
+ ssbe = int(constants.get("STEPPER_STEP_BOTH_EDGE", "0"))
+ sbe = int(constants.get("STEPPER_BOTH_EDGE", "0"))
+ sou = int(constants.get("STEPPER_OPTIMIZED_UNSTEP", "0"))
want_both_edges = self._req_step_both_edge
if self._step_pulse_duration > MIN_BOTH_EDGE_DURATION:
# If user has requested a very large step pulse duration
# then disable step on both edges (rise and fall times may
# not be symmetric)
want_both_edges = False
- elif sbe and self._step_pulse_duration>MIN_OPTIMIZED_BOTH_EDGE_DURATION:
+ elif sbe and self._step_pulse_duration > MIN_OPTIMIZED_BOTH_EDGE_DURATION:
# Older MCU and user has requested large pulse duration
want_both_edges = False
elif not sbe and not ssbe:
@@ -102,43 +121,57 @@ class MCU_stepper:
invert_step = -1
if sbe:
# Older MCU requires setting step_pulse_ticks=0 to enable
- self._step_pulse_duration = 0.
+ self._step_pulse_duration = 0.0
# Configure stepper object
step_pulse_ticks = self._mcu.seconds_to_clock(self._step_pulse_duration)
self._mcu.add_config_cmd(
"config_stepper oid=%d step_pin=%s dir_pin=%s invert_step=%d"
- " step_pulse_ticks=%u" % (self._oid, self._step_pin, self._dir_pin,
- invert_step, step_pulse_ticks))
- self._mcu.add_config_cmd("reset_step_clock oid=%d clock=0"
- % (self._oid,), on_restart=True)
+ " step_pulse_ticks=%u"
+ % (self._oid, self._step_pin, self._dir_pin, invert_step, step_pulse_ticks)
+ )
+ self._mcu.add_config_cmd(
+ "reset_step_clock oid=%d clock=0" % (self._oid,), on_restart=True
+ )
step_cmd_tag = self._mcu.lookup_command(
- "queue_step oid=%c interval=%u count=%hu add=%hi").get_command_tag()
+ "queue_step oid=%c interval=%u count=%hu add=%hi"
+ ).get_command_tag()
dir_cmd_tag = self._mcu.lookup_command(
- "set_next_step_dir oid=%c dir=%c").get_command_tag()
+ "set_next_step_dir oid=%c dir=%c"
+ ).get_command_tag()
self._reset_cmd_tag = self._mcu.lookup_command(
- "reset_step_clock oid=%c clock=%u").get_command_tag()
+ "reset_step_clock oid=%c clock=%u"
+ ).get_command_tag()
self._get_position_cmd = self._mcu.lookup_query_command(
"stepper_get_position oid=%c",
- "stepper_position oid=%c pos=%i", oid=self._oid)
+ "stepper_position oid=%c pos=%i",
+ oid=self._oid,
+ )
max_error = self._mcu.get_max_stepper_error()
max_error_ticks = self._mcu.seconds_to_clock(max_error)
ffi_main, ffi_lib = chelper.get_ffi()
- ffi_lib.stepcompress_fill(self._stepqueue, max_error_ticks,
- step_cmd_tag, dir_cmd_tag)
+ ffi_lib.stepcompress_fill(
+ self._stepqueue, max_error_ticks, step_cmd_tag, dir_cmd_tag
+ )
+
def get_oid(self):
return self._oid
+
def get_step_dist(self):
return self._step_dist
+
def get_rotation_distance(self):
return self._rotation_dist, self._steps_per_rotation
+
def set_rotation_distance(self, rotation_dist):
mcu_pos = self.get_mcu_position()
self._rotation_dist = rotation_dist
self._step_dist = rotation_dist / self._steps_per_rotation
self.set_stepper_kinematics(self._stepper_kinematics)
self._set_mcu_position(mcu_pos)
+
def get_dir_inverted(self):
return self._invert_dir, self._orig_invert_dir
+
def set_dir_inverted(self, invert_dir):
invert_dir = not not invert_dir
if invert_dir == self._invert_dir:
@@ -147,45 +180,57 @@ class MCU_stepper:
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.stepcompress_set_invert_sdir(self._stepqueue, invert_dir)
self._mcu.get_printer().send_event("stepper:set_dir_inverted", self)
+
def calc_position_from_coord(self, coord):
ffi_main, ffi_lib = chelper.get_ffi()
return ffi_lib.itersolve_calc_position_from_coord(
- self._stepper_kinematics, coord[0], coord[1], coord[2])
+ self._stepper_kinematics, coord[0], coord[1], coord[2]
+ )
+
def set_position(self, coord):
mcu_pos = self.get_mcu_position()
sk = self._stepper_kinematics
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.itersolve_set_position(sk, coord[0], coord[1], coord[2])
self._set_mcu_position(mcu_pos)
+
def get_commanded_position(self):
ffi_main, ffi_lib = chelper.get_ffi()
return ffi_lib.itersolve_get_commanded_pos(self._stepper_kinematics)
+
def get_mcu_position(self, cmd_pos=None):
if cmd_pos is None:
cmd_pos = self.get_commanded_position()
mcu_pos_dist = cmd_pos + self._mcu_position_offset
mcu_pos = mcu_pos_dist / self._step_dist
- if mcu_pos >= 0.:
+ if mcu_pos >= 0.0:
return int(mcu_pos + 0.5)
return int(mcu_pos - 0.5)
+
def _set_mcu_position(self, mcu_pos):
mcu_pos_dist = mcu_pos * self._step_dist
self._mcu_position_offset = mcu_pos_dist - self.get_commanded_position()
+
def get_past_mcu_position(self, print_time):
clock = self._mcu.print_time_to_clock(print_time)
ffi_main, ffi_lib = chelper.get_ffi()
pos = ffi_lib.stepcompress_find_past_position(self._stepqueue, clock)
return int(pos)
+
def mcu_to_commanded_position(self, mcu_pos):
return mcu_pos * self._step_dist - self._mcu_position_offset
+
def dump_steps(self, count, start_clock, end_clock):
ffi_main, ffi_lib = chelper.get_ffi()
- data = ffi_main.new('struct pull_history_steps[]', count)
- count = ffi_lib.stepcompress_extract_old(self._stepqueue, data, count,
- start_clock, end_clock)
+ data = ffi_main.new("struct pull_history_steps[]", count)
+ count = ffi_lib.stepcompress_extract_old(
+ self._stepqueue, data, count, start_clock, end_clock
+ )
return (data, count)
+
def get_stepper_kinematics(self):
return self._stepper_kinematics
+
def set_stepper_kinematics(self, sk):
old_sk = self._stepper_kinematics
mcu_pos = 0
@@ -197,6 +242,7 @@ class MCU_stepper:
self.set_trapq(self._trapq)
self._set_mcu_position(mcu_pos)
return old_sk
+
def note_homing_end(self):
ffi_main, ffi_lib = chelper.get_ffi()
ret = ffi_lib.stepcompress_reset(self._stepqueue, 0)
@@ -207,24 +253,26 @@ class MCU_stepper:
if ret:
raise error("Internal error in stepcompress")
self._query_mcu_position()
+
def _query_mcu_position(self):
if self._mcu.is_fileoutput():
return
params = self._get_position_cmd.send([self._oid])
- last_pos = params['pos']
+ last_pos = params["pos"]
if self._invert_dir:
last_pos = -last_pos
- print_time = self._mcu.estimated_print_time(params['#receive_time'])
+ print_time = self._mcu.estimated_print_time(params["#receive_time"])
clock = self._mcu.print_time_to_clock(print_time)
ffi_main, ffi_lib = chelper.get_ffi()
- ret = ffi_lib.stepcompress_set_last_position(self._stepqueue, clock,
- last_pos)
+ ret = ffi_lib.stepcompress_set_last_position(self._stepqueue, clock, last_pos)
if ret:
raise error("Internal error in stepcompress")
self._set_mcu_position(last_pos)
self._mcu.get_printer().send_event("stepper:sync_mcu_position", self)
+
def get_trapq(self):
return self._trapq
+
def set_trapq(self, tq):
ffi_main, ffi_lib = chelper.get_ffi()
if tq is None:
@@ -233,8 +281,10 @@ class MCU_stepper:
old_tq = self._trapq
self._trapq = tq
return old_tq
+
def add_active_callback(self, cb):
self._active_callbacks.append(cb)
+
def generate_steps(self, flush_time):
# Check for activity if necessary
if self._active_callbacks:
@@ -250,64 +300,80 @@ class MCU_stepper:
ret = self._itersolve_generate_steps(sk, flush_time)
if ret:
raise error("Internal error in stepcompress")
+
def is_active_axis(self, axis):
ffi_main, ffi_lib = chelper.get_ffi()
a = axis.encode()
return ffi_lib.itersolve_is_active_axis(self._stepper_kinematics, a)
+
# Helper code to build a stepper object from a config section
def PrinterStepper(config, units_in_radians=False):
printer = config.get_printer()
name = config.get_name()
# Stepper definition
- ppins = printer.lookup_object('pins')
- step_pin = config.get('step_pin')
+ ppins = printer.lookup_object("pins")
+ step_pin = config.get("step_pin")
step_pin_params = ppins.lookup_pin(step_pin, can_invert=True)
- dir_pin = config.get('dir_pin')
+ dir_pin = config.get("dir_pin")
dir_pin_params = ppins.lookup_pin(dir_pin, can_invert=True)
rotation_dist, steps_per_rotation = parse_step_distance(
- config, units_in_radians, True)
- step_pulse_duration = config.getfloat('step_pulse_duration', None,
- minval=0., maxval=.001)
- mcu_stepper = MCU_stepper(name, step_pin_params, dir_pin_params,
- rotation_dist, steps_per_rotation,
- step_pulse_duration, units_in_radians)
+ config, units_in_radians, True
+ )
+ step_pulse_duration = config.getfloat(
+ "step_pulse_duration", None, minval=0.0, maxval=0.001
+ )
+ mcu_stepper = MCU_stepper(
+ name,
+ step_pin_params,
+ dir_pin_params,
+ rotation_dist,
+ steps_per_rotation,
+ step_pulse_duration,
+ units_in_radians,
+ )
# Register with helper modules
- for mname in ['stepper_enable', 'force_move', 'motion_report']:
+ for mname in ["stepper_enable", "force_move", "motion_report"]:
m = printer.load_object(config, mname)
m.register_stepper(config, mcu_stepper)
return mcu_stepper
+
# Parse stepper gear_ratio config parameter
def parse_gear_ratio(config, note_valid):
- gear_ratio = config.getlists('gear_ratio', (), seps=(':', ','), count=2,
- parser=float, note_valid=note_valid)
- result = 1.
+ gear_ratio = config.getlists(
+ "gear_ratio", (), seps=(":", ","), count=2, parser=float, note_valid=note_valid
+ )
+ result = 1.0
for g1, g2 in gear_ratio:
result *= g1 / g2
return result
+
# Obtain "step distance" information from a config section
def parse_step_distance(config, units_in_radians=None, note_valid=False):
# Check rotation_distance and gear_ratio
if units_in_radians is None:
# Caller doesn't know if units are in radians - infer it
- rd = config.get('rotation_distance', None, note_valid=False)
- gr = config.get('gear_ratio', None, note_valid=False)
+ rd = config.get("rotation_distance", None, note_valid=False)
+ gr = config.get("gear_ratio", None, note_valid=False)
units_in_radians = rd is None and gr is not None
if units_in_radians:
- rotation_dist = 2. * math.pi
- config.get('gear_ratio', note_valid=note_valid)
+ rotation_dist = 2.0 * math.pi
+ config.get("gear_ratio", note_valid=note_valid)
else:
- rotation_dist = config.getfloat('rotation_distance', above=0.,
- note_valid=note_valid)
+ rotation_dist = config.getfloat(
+ "rotation_distance", above=0.0, note_valid=note_valid
+ )
# Check microsteps and full_steps_per_rotation
- microsteps = config.getint('microsteps', minval=1, note_valid=note_valid)
- full_steps = config.getint('full_steps_per_rotation', 200, minval=1,
- note_valid=note_valid)
+ microsteps = config.getint("microsteps", minval=1, note_valid=note_valid)
+ full_steps = config.getint(
+ "full_steps_per_rotation", 200, minval=1, note_valid=note_valid
+ )
if full_steps % 4:
- raise config.error("full_steps_per_rotation invalid in section '%s'"
- % (config.get_name(),))
+ raise config.error(
+ "full_steps_per_rotation invalid in section '%s'" % (config.get_name(),)
+ )
gearing = parse_gear_ratio(config, note_valid)
return rotation_dist, full_steps * microsteps * gearing
@@ -316,115 +382,151 @@ def parse_step_distance(config, units_in_radians=None, note_valid=False):
# Stepper controlled rails
######################################################################
+
# A motor control carriage with one (or more) steppers and one (or more)
# endstops.
class GenericPrinterRail:
- def __init__(self, config, need_position_minmax=True,
- default_position_endstop=None, units_in_radians=False):
+ def __init__(
+ self,
+ config,
+ need_position_minmax=True,
+ default_position_endstop=None,
+ units_in_radians=False,
+ ):
self.stepper_units_in_radians = units_in_radians
self.printer = config.get_printer()
self.name = config.get_name()
self.steppers = []
self.endstops = []
self.endstop_map = {}
- self.endstop_pin = config.get('endstop_pin')
+ self.endstop_pin = config.get("endstop_pin")
# Primary endstop position
- self.query_endstops = self.printer.load_object(config, 'query_endstops')
+ self.query_endstops = self.printer.load_object(config, "query_endstops")
mcu_endstop = self.lookup_endstop(self.endstop_pin, self.name)
if hasattr(mcu_endstop, "get_position_endstop"):
self.position_endstop = mcu_endstop.get_position_endstop()
elif default_position_endstop is None:
- self.position_endstop = config.getfloat('position_endstop')
+ self.position_endstop = config.getfloat("position_endstop")
else:
self.position_endstop = config.getfloat(
- 'position_endstop', default_position_endstop)
+ "position_endstop", default_position_endstop
+ )
# Axis range
if need_position_minmax:
- self.position_min = config.getfloat('position_min', 0.)
- self.position_max = config.getfloat(
- 'position_max', above=self.position_min)
+ self.position_min = config.getfloat("position_min", 0.0)
+ self.position_max = config.getfloat("position_max", above=self.position_min)
else:
- self.position_min = 0.
+ self.position_min = 0.0
self.position_max = self.position_endstop
- if (self.position_endstop < self.position_min
- or self.position_endstop > self.position_max):
+ if (
+ self.position_endstop < self.position_min
+ or self.position_endstop > self.position_max
+ ):
raise config.error(
"position_endstop in section '%s' must be between"
- " position_min and position_max" % config.get_name())
+ " position_min and position_max" % config.get_name()
+ )
# Homing mechanics
- self.homing_speed = config.getfloat('homing_speed', 5.0, above=0.)
+ self.homing_speed = config.getfloat("homing_speed", 5.0, above=0.0)
self.second_homing_speed = config.getfloat(
- 'second_homing_speed', self.homing_speed/2., above=0.)
+ "second_homing_speed", self.homing_speed / 2.0, above=0.0
+ )
self.homing_retract_speed = config.getfloat(
- 'homing_retract_speed', self.homing_speed, above=0.)
+ "homing_retract_speed", self.homing_speed, above=0.0
+ )
self.homing_retract_dist = config.getfloat(
- 'homing_retract_dist', 5., minval=0.)
- self.homing_positive_dir = config.getboolean(
- 'homing_positive_dir', None)
+ "homing_retract_dist", 5.0, minval=0.0
+ )
+ self.homing_positive_dir = config.getboolean("homing_positive_dir", None)
if self.homing_positive_dir is None:
axis_len = self.position_max - self.position_min
- if self.position_endstop <= self.position_min + axis_len / 4.:
+ if self.position_endstop <= self.position_min + axis_len / 4.0:
self.homing_positive_dir = False
- elif self.position_endstop >= self.position_max - axis_len / 4.:
+ elif self.position_endstop >= self.position_max - axis_len / 4.0:
self.homing_positive_dir = True
else:
raise config.error(
"Unable to infer homing_positive_dir in section '%s'"
- % (config.get_name(),))
- config.getboolean('homing_positive_dir', self.homing_positive_dir)
- elif ((self.homing_positive_dir
- and self.position_endstop == self.position_min)
- or (not self.homing_positive_dir
- and self.position_endstop == self.position_max)):
+ % (config.get_name(),)
+ )
+ config.getboolean("homing_positive_dir", self.homing_positive_dir)
+ elif (
+ self.homing_positive_dir and self.position_endstop == self.position_min
+ ) or (
+ not self.homing_positive_dir and self.position_endstop == self.position_max
+ ):
raise config.error(
"Invalid homing_positive_dir / position_endstop in '%s'"
- % (config.get_name(),))
+ % (config.get_name(),)
+ )
+
def get_name(self, short=False):
if short:
- if self.name.startswith('stepper'):
+ if self.name.startswith("stepper"):
# Skip an extra symbol after 'stepper'
return self.name[8:]
return self.name.split()[-1]
return self.name
+
def get_range(self):
return self.position_min, self.position_max
+
def get_homing_info(self):
- homing_info = collections.namedtuple('homing_info', [
- 'speed', 'position_endstop', 'retract_speed', 'retract_dist',
- 'positive_dir', 'second_homing_speed'])(
- self.homing_speed, self.position_endstop,
- self.homing_retract_speed, self.homing_retract_dist,
- self.homing_positive_dir, self.second_homing_speed)
+ homing_info = collections.namedtuple(
+ "homing_info",
+ [
+ "speed",
+ "position_endstop",
+ "retract_speed",
+ "retract_dist",
+ "positive_dir",
+ "second_homing_speed",
+ ],
+ )(
+ self.homing_speed,
+ self.position_endstop,
+ self.homing_retract_speed,
+ self.homing_retract_dist,
+ self.homing_positive_dir,
+ self.second_homing_speed,
+ )
return homing_info
+
def get_steppers(self):
return list(self.steppers)
+
def get_endstops(self):
return list(self.endstops)
+
def lookup_endstop(self, endstop_pin, name):
- ppins = self.printer.lookup_object('pins')
+ ppins = self.printer.lookup_object("pins")
pin_params = ppins.parse_pin(endstop_pin, True, True)
# Normalize pin name
- pin_name = "%s:%s" % (pin_params['chip_name'], pin_params['pin'])
+ pin_name = "%s:%s" % (pin_params["chip_name"], pin_params["pin"])
# Look for already-registered endstop
endstop = self.endstop_map.get(pin_name, None)
if endstop is None:
# New endstop, register it
- mcu_endstop = ppins.setup_pin('endstop', endstop_pin)
- self.endstop_map[pin_name] = {'endstop': mcu_endstop,
- 'invert': pin_params['invert'],
- 'pullup': pin_params['pullup']}
+ mcu_endstop = ppins.setup_pin("endstop", endstop_pin)
+ self.endstop_map[pin_name] = {
+ "endstop": mcu_endstop,
+ "invert": pin_params["invert"],
+ "pullup": pin_params["pullup"],
+ }
self.endstops.append((mcu_endstop, name))
self.query_endstops.register_endstop(mcu_endstop, name)
else:
- mcu_endstop = endstop['endstop']
- changed_invert = pin_params['invert'] != endstop['invert']
- changed_pullup = pin_params['pullup'] != endstop['pullup']
+ mcu_endstop = endstop["endstop"]
+ changed_invert = pin_params["invert"] != endstop["invert"]
+ changed_pullup = pin_params["pullup"] != endstop["pullup"]
if changed_invert or changed_pullup:
raise self.printer.config_error(
- "Printer rail %s shared endstop pin %s "
- "must specify the same pullup/invert settings" % (
- self.get_name(), pin_name))
+ "Printer rail %s shared endstop pin %s "
+ "must specify the same pullup/invert settings"
+ % (self.get_name(), pin_name)
+ )
return mcu_endstop
+
def add_stepper(self, stepper, endstop_pin=None, endstop_name=None):
if not self.steppers:
self.get_commanded_position = stepper.get_commanded_position
@@ -432,41 +534,58 @@ class GenericPrinterRail:
self.steppers.append(stepper)
if endstop_pin is not None:
mcu_endstop = self.lookup_endstop(
- endstop_pin, endstop_name or stepper.get_name(short=True))
+ endstop_pin, endstop_name or stepper.get_name(short=True)
+ )
else:
mcu_endstop = self.lookup_endstop(self.endstop_pin, self.name)
mcu_endstop.add_stepper(stepper)
+
def add_stepper_from_config(self, config):
stepper = PrinterStepper(config, self.stepper_units_in_radians)
- self.add_stepper(stepper, config.get('endstop_pin', None))
+ self.add_stepper(stepper, config.get("endstop_pin", None))
+
def setup_itersolve(self, alloc_func, *params):
for stepper in self.steppers:
stepper.setup_itersolve(alloc_func, *params)
+
def generate_steps(self, flush_time):
for stepper in self.steppers:
stepper.generate_steps(flush_time)
+
def set_trapq(self, trapq):
for stepper in self.steppers:
stepper.set_trapq(trapq)
+
def set_position(self, coord):
for stepper in self.steppers:
stepper.set_position(coord)
-def LookupRail(config, need_position_minmax=True,
- default_position_endstop=None, units_in_radians=False):
- rail = GenericPrinterRail(config, need_position_minmax,
- default_position_endstop, units_in_radians)
+
+def LookupRail(
+ config,
+ need_position_minmax=True,
+ default_position_endstop=None,
+ units_in_radians=False,
+):
+ rail = GenericPrinterRail(
+ config, need_position_minmax, default_position_endstop, units_in_radians
+ )
rail.add_stepper_from_config(config)
return rail
+
# Wrapper for dual stepper motor support
-def LookupMultiRail(config, need_position_minmax=True,
- default_position_endstop=None, units_in_radians=False):
- rail = LookupRail(config, need_position_minmax,
- default_position_endstop, units_in_radians)
+def LookupMultiRail(
+ config,
+ need_position_minmax=True,
+ default_position_endstop=None,
+ units_in_radians=False,
+):
+ rail = LookupRail(
+ config, need_position_minmax, default_position_endstop, units_in_radians
+ )
for i in range(1, 99):
if not config.has_section(config.get_name() + str(i)):
break
- rail.add_stepper_from_config(
- config.getsection(config.get_name() + str(i)))
+ rail.add_stepper_from_config(config.getsection(config.get_name() + str(i)))
return rail
diff --git a/klippy/toolhead.py b/klippy/toolhead.py
index f835977c..13ef7e2b 100644
--- a/klippy/toolhead.py
+++ b/klippy/toolhead.py
@@ -10,6 +10,7 @@ import mcu, chelper, kinematics.extruder
# mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in
# seconds), _r is ratio (scalar between 0.0 and 1.0)
+
# Class to track each move request
class Move:
def __init__(self, toolhead, start_pos, end_pos, speed):
@@ -22,32 +23,32 @@ class Move:
velocity = min(speed, toolhead.max_velocity)
self.is_kinematic_move = True
self.axes_d = axes_d = [ep - sp for sp, ep in zip(start_pos, end_pos)]
- self.move_d = move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
- if move_d < .000000001:
+ self.move_d = move_d = math.sqrt(sum([d * d for d in axes_d[:3]]))
+ if move_d < 0.000000001:
# Extrude only move
- self.end_pos = ((start_pos[0], start_pos[1], start_pos[2])
- + self.end_pos[3:])
- axes_d[0] = axes_d[1] = axes_d[2] = 0.
+ self.end_pos = (start_pos[0], start_pos[1], start_pos[2]) + self.end_pos[3:]
+ axes_d[0] = axes_d[1] = axes_d[2] = 0.0
self.move_d = move_d = max([abs(ad) for ad in axes_d[3:]])
- inv_move_d = 0.
+ inv_move_d = 0.0
if move_d:
- inv_move_d = 1. / move_d
+ inv_move_d = 1.0 / move_d
self.accel = 99999999.9
velocity = speed
self.is_kinematic_move = False
else:
- inv_move_d = 1. / move_d
+ inv_move_d = 1.0 / move_d
self.axes_r = [d * inv_move_d for d in axes_d]
self.min_move_t = move_d / velocity
# Junction speeds are tracked in velocity squared. The
# delta_v2 is the maximum amount of this squared-velocity that
# can change in this move.
- self.max_start_v2 = 0.
+ self.max_start_v2 = 0.0
self.max_cruise_v2 = velocity**2
self.delta_v2 = 2.0 * move_d * self.accel
- self.max_smoothed_v2 = 0.
+ self.max_smoothed_v2 = 0.0
self.smooth_delta_v2 = 2.0 * move_d * toolhead.max_accel_to_decel
self.next_junction_v2 = 999999999.9
+
def limit_speed(self, speed, accel):
speed2 = speed**2
if speed2 < self.max_cruise_v2:
@@ -56,49 +57,68 @@ class Move:
self.accel = min(self.accel, accel)
self.delta_v2 = 2.0 * self.move_d * self.accel
self.smooth_delta_v2 = min(self.smooth_delta_v2, self.delta_v2)
+
def limit_next_junction_speed(self, speed):
self.next_junction_v2 = min(self.next_junction_v2, speed**2)
+
def move_error(self, msg="Move out of range"):
ep = self.end_pos
m = "%s: %.3f %.3f %.3f [%.3f]" % (msg, ep[0], ep[1], ep[2], ep[3])
return self.toolhead.printer.command_error(m)
+
def calc_junction(self, prev_move):
if not self.is_kinematic_move or not prev_move.is_kinematic_move:
return
# Allow extra axes to calculate maximum junction
- ea_v2 = [ea.calc_junction(prev_move, self, e_index+3)
- for e_index, ea in enumerate(self.toolhead.extra_axes)]
- max_start_v2 = min([self.max_cruise_v2,
- prev_move.max_cruise_v2, prev_move.next_junction_v2,
- prev_move.max_start_v2 + prev_move.delta_v2]
- + ea_v2)
+ ea_v2 = [
+ ea.calc_junction(prev_move, self, e_index + 3)
+ for e_index, ea in enumerate(self.toolhead.extra_axes)
+ ]
+ max_start_v2 = min(
+ [
+ self.max_cruise_v2,
+ prev_move.max_cruise_v2,
+ prev_move.next_junction_v2,
+ prev_move.max_start_v2 + prev_move.delta_v2,
+ ]
+ + ea_v2
+ )
# Find max velocity using "approximated centripetal velocity"
axes_r = self.axes_r
prev_axes_r = prev_move.axes_r
- junction_cos_theta = -(axes_r[0] * prev_axes_r[0]
- + axes_r[1] * prev_axes_r[1]
- + axes_r[2] * prev_axes_r[2])
- sin_theta_d2 = math.sqrt(max(0.5*(1.0-junction_cos_theta), 0.))
- cos_theta_d2 = math.sqrt(max(0.5*(1.0+junction_cos_theta), 0.))
- one_minus_sin_theta_d2 = 1. - sin_theta_d2
- if one_minus_sin_theta_d2 > 0. and cos_theta_d2 > 0.:
+ junction_cos_theta = -(
+ axes_r[0] * prev_axes_r[0]
+ + axes_r[1] * prev_axes_r[1]
+ + axes_r[2] * prev_axes_r[2]
+ )
+ sin_theta_d2 = math.sqrt(max(0.5 * (1.0 - junction_cos_theta), 0.0))
+ cos_theta_d2 = math.sqrt(max(0.5 * (1.0 + junction_cos_theta), 0.0))
+ one_minus_sin_theta_d2 = 1.0 - sin_theta_d2
+ if one_minus_sin_theta_d2 > 0.0 and cos_theta_d2 > 0.0:
R_jd = sin_theta_d2 / one_minus_sin_theta_d2
move_jd_v2 = R_jd * self.junction_deviation * self.accel
pmove_jd_v2 = R_jd * prev_move.junction_deviation * prev_move.accel
# Approximated circle must contact moves no further than mid-move
# centripetal_v2 = .5 * self.move_d * self.accel * tan_theta_d2
- quarter_tan_theta_d2 = .25 * sin_theta_d2 / cos_theta_d2
+ quarter_tan_theta_d2 = 0.25 * sin_theta_d2 / cos_theta_d2
move_centripetal_v2 = self.delta_v2 * quarter_tan_theta_d2
pmove_centripetal_v2 = prev_move.delta_v2 * quarter_tan_theta_d2
- max_start_v2 = min(max_start_v2, move_jd_v2, pmove_jd_v2,
- move_centripetal_v2, pmove_centripetal_v2)
+ max_start_v2 = min(
+ max_start_v2,
+ move_jd_v2,
+ pmove_jd_v2,
+ move_centripetal_v2,
+ pmove_centripetal_v2,
+ )
# Apply limits
self.max_start_v2 = max_start_v2
self.max_smoothed_v2 = min(
- max_start_v2, prev_move.max_smoothed_v2 + prev_move.smooth_delta_v2)
+ max_start_v2, prev_move.max_smoothed_v2 + prev_move.smooth_delta_v2
+ )
+
def set_junction(self, start_v2, cruise_v2, end_v2):
# Determine accel, cruise, and decel portions of the move distance
- half_inv_accel = .5 / self.accel
+ half_inv_accel = 0.5 / self.accel
accel_d = (cruise_v2 - start_v2) * half_inv_accel
decel_d = (cruise_v2 - end_v2) * half_inv_accel
cruise_d = self.move_d - accel_d - decel_d
@@ -112,23 +132,29 @@ class Move:
self.cruise_t = cruise_d / cruise_v
self.decel_t = decel_d / ((end_v + cruise_v) * 0.5)
+
LOOKAHEAD_FLUSH_TIME = 0.250
+
# Class to track a list of pending move requests and to facilitate
# "look-ahead" across moves to reduce acceleration between moves.
class LookAheadQueue:
def __init__(self):
self.queue = []
self.junction_flush = LOOKAHEAD_FLUSH_TIME
+
def reset(self):
del self.queue[:]
self.junction_flush = LOOKAHEAD_FLUSH_TIME
+
def set_flush_time(self, flush_time):
self.junction_flush = flush_time
+
def get_last(self):
if self.queue:
return self.queue[-1]
return None
+
def flush(self, lazy=False):
self.junction_flush = LOOKAHEAD_FLUSH_TIME
update_flush_count = lazy
@@ -138,8 +164,8 @@ class LookAheadQueue:
# junction speed assuming the robot comes to a complete stop
# after the last move.
delayed = []
- next_end_v2 = next_smoothed_v2 = peak_cruise_v2 = 0.
- for i in range(flush_count-1, -1, -1):
+ next_end_v2 = next_smoothed_v2 = peak_cruise_v2 = 0.0
+ for i in range(flush_count - 1, -1, -1):
move = queue[i]
reachable_start_v2 = next_end_v2 + move.delta_v2
start_v2 = min(move.max_start_v2, reachable_start_v2)
@@ -147,29 +173,34 @@ class LookAheadQueue:
smoothed_v2 = min(move.max_smoothed_v2, reachable_smoothed_v2)
if smoothed_v2 < reachable_smoothed_v2:
# It's possible for this move to accelerate
- if (smoothed_v2 + move.smooth_delta_v2 > next_smoothed_v2
- or delayed):
+ if smoothed_v2 + move.smooth_delta_v2 > next_smoothed_v2 or delayed:
# This move can decelerate or this is a full accel
# move after a full decel move
if update_flush_count and peak_cruise_v2:
flush_count = i
update_flush_count = False
- peak_cruise_v2 = min(move.max_cruise_v2, (
- smoothed_v2 + reachable_smoothed_v2) * .5)
+ peak_cruise_v2 = min(
+ move.max_cruise_v2, (smoothed_v2 + reachable_smoothed_v2) * 0.5
+ )
if delayed:
# Propagate peak_cruise_v2 to any delayed moves
if not update_flush_count and i < flush_count:
mc_v2 = peak_cruise_v2
for m, ms_v2, me_v2 in reversed(delayed):
mc_v2 = min(mc_v2, ms_v2)
- m.set_junction(min(ms_v2, mc_v2), mc_v2
- , min(me_v2, mc_v2))
+ m.set_junction(
+ min(ms_v2, mc_v2), mc_v2, min(me_v2, mc_v2)
+ )
del delayed[:]
if not update_flush_count and i < flush_count:
- cruise_v2 = min((start_v2 + reachable_start_v2) * .5
- , move.max_cruise_v2, peak_cruise_v2)
- move.set_junction(min(start_v2, cruise_v2), cruise_v2
- , min(next_end_v2, cruise_v2))
+ cruise_v2 = min(
+ (start_v2 + reachable_start_v2) * 0.5,
+ move.max_cruise_v2,
+ peak_cruise_v2,
+ )
+ move.set_junction(
+ min(start_v2, cruise_v2), cruise_v2, min(next_end_v2, cruise_v2)
+ )
else:
# Delay calculating this move until peak_cruise_v2 is known
delayed.append((move, start_v2, next_end_v2))
@@ -181,6 +212,7 @@ class LookAheadQueue:
res = queue[:flush_count]
del queue[:flush_count]
return res
+
def add_move(self, move):
self.queue.append(move)
if len(self.queue) == 1:
@@ -188,7 +220,8 @@ class LookAheadQueue:
move.calc_junction(self.queue[-2])
self.junction_flush -= move.min_move_t
# Check if enough moves have been queued to reach the target flush time.
- return self.junction_flush <= 0.
+ return self.junction_flush <= 0.0
+
BUFFER_TIME_LOW = 1.0
BUFFER_TIME_HIGH = 2.0
@@ -199,58 +232,57 @@ BGFLUSH_EXTRA_TIME = 0.250
MIN_KIN_TIME = 0.100
MOVE_BATCH_TIME = 0.500
STEPCOMPRESS_FLUSH_TIME = 0.050
-SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c
-MOVE_HISTORY_EXPIRE = 30.
+SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c
+MOVE_HISTORY_EXPIRE = 30.0
DRIP_SEGMENT_TIME = 0.050
DRIP_TIME = 0.100
+
# Main code to track events (and their timing) on the printer toolhead
class ToolHead:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
- self.all_mcus = [
- m for n, m in self.printer.lookup_objects(module='mcu')]
+ self.all_mcus = [m for n, m in self.printer.lookup_objects(module="mcu")]
self.mcu = self.all_mcus[0]
self.lookahead = LookAheadQueue()
self.lookahead.set_flush_time(BUFFER_TIME_HIGH)
- self.commanded_pos = [0., 0., 0., 0.]
+ self.commanded_pos = [0.0, 0.0, 0.0, 0.0]
# Velocity and acceleration control
- self.max_velocity = config.getfloat('max_velocity', above=0.)
- self.max_accel = config.getfloat('max_accel', above=0.)
+ self.max_velocity = config.getfloat("max_velocity", above=0.0)
+ self.max_accel = config.getfloat("max_accel", above=0.0)
min_cruise_ratio = 0.5
- if config.getfloat('minimum_cruise_ratio', None) is None:
- req_accel_to_decel = config.getfloat('max_accel_to_decel', None,
- above=0.)
+ if config.getfloat("minimum_cruise_ratio", None) is None:
+ req_accel_to_decel = config.getfloat("max_accel_to_decel", None, above=0.0)
if req_accel_to_decel is not None:
- config.deprecate('max_accel_to_decel')
- min_cruise_ratio = 1. - min(1., (req_accel_to_decel
- / self.max_accel))
- self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio',
- min_cruise_ratio,
- below=1., minval=0.)
+ config.deprecate("max_accel_to_decel")
+ min_cruise_ratio = 1.0 - min(1.0, (req_accel_to_decel / self.max_accel))
+ self.min_cruise_ratio = config.getfloat(
+ "minimum_cruise_ratio", min_cruise_ratio, below=1.0, minval=0.0
+ )
self.square_corner_velocity = config.getfloat(
- 'square_corner_velocity', 5., minval=0.)
- self.junction_deviation = self.max_accel_to_decel = 0.
+ "square_corner_velocity", 5.0, minval=0.0
+ )
+ self.junction_deviation = self.max_accel_to_decel = 0.0
self._calc_junction_deviation()
# Input stall detection
- self.check_stall_time = 0.
+ self.check_stall_time = 0.0
self.print_stall = 0
# Input pause tracking
self.can_pause = True
if self.mcu.is_fileoutput():
self.can_pause = False
- self.need_check_pause = -1.
+ self.need_check_pause = -1.0
# Print time tracking
- self.print_time = 0.
+ self.print_time = 0.0
self.special_queuing_state = "NeedPrime"
self.priming_timer = None
# Flush tracking
self.flush_timer = self.reactor.register_timer(self._flush_handler)
self.do_kick_flush_timer = True
- self.last_flush_time = self.min_restart_time = 0.
- self.need_flush_time = self.step_gen_time = self.clear_history_time = 0.
+ self.last_flush_time = self.min_restart_time = 0.0
+ self.need_flush_time = self.step_gen_time = self.clear_history_time = 0.0
# Kinematic step generation scan window time tracking
self.kin_flush_delay = SDS_CHECK_TIME
self.kin_flush_times = []
@@ -263,42 +295,52 @@ class ToolHead:
self.step_generators = []
self.flush_trapqs = [self.trapq]
# Create kinematics class
- gcode = self.printer.lookup_object('gcode')
+ gcode = self.printer.lookup_object("gcode")
self.Coord = gcode.Coord
extruder = kinematics.extruder.DummyExtruder(self.printer)
self.extra_axes = [extruder]
- kin_name = config.get('kinematics')
+ kin_name = config.get("kinematics")
try:
- mod = importlib.import_module('kinematics.' + kin_name)
+ mod = importlib.import_module("kinematics." + kin_name)
self.kin = mod.load_kinematics(self, config)
except config.error as e:
raise
- except self.printer.lookup_object('pins').error as e:
+ except self.printer.lookup_object("pins").error as e:
raise
except:
msg = "Error loading kinematics '%s'" % (kin_name,)
logging.exception(msg)
raise config.error(msg)
# Register commands
- gcode.register_command('G4', self.cmd_G4)
- gcode.register_command('M400', self.cmd_M400)
- gcode.register_command('SET_VELOCITY_LIMIT',
- self.cmd_SET_VELOCITY_LIMIT,
- desc=self.cmd_SET_VELOCITY_LIMIT_help)
- gcode.register_command('M204', self.cmd_M204)
- self.printer.register_event_handler("klippy:shutdown",
- self._handle_shutdown)
+ gcode.register_command("G4", self.cmd_G4)
+ gcode.register_command("M400", self.cmd_M400)
+ gcode.register_command(
+ "SET_VELOCITY_LIMIT",
+ self.cmd_SET_VELOCITY_LIMIT,
+ desc=self.cmd_SET_VELOCITY_LIMIT_help,
+ )
+ gcode.register_command("M204", self.cmd_M204)
+ self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
# Load some default modules
- modules = ["gcode_move", "homing", "idle_timeout", "statistics",
- "manual_probe", "tuning_tower", "garbage_collection"]
+ modules = [
+ "gcode_move",
+ "homing",
+ "idle_timeout",
+ "statistics",
+ "manual_probe",
+ "tuning_tower",
+ "garbage_collection",
+ ]
for module_name in modules:
self.printer.load_object(config, module_name)
+
# Print time and flush tracking
def _advance_flush_time(self, flush_time):
flush_time = max(flush_time, self.last_flush_time)
# Generate steps via itersolve
- sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME,
- self.print_time - self.kin_flush_delay)
+ sg_flush_want = min(
+ flush_time + STEPCOMPRESS_FLUSH_TIME, self.print_time - self.kin_flush_delay
+ )
sg_flush_time = max(sg_flush_want, flush_time)
for sg in self.step_generators:
sg(sg_flush_time)
@@ -314,6 +356,7 @@ class ToolHead:
for m in self.all_mcus:
m.flush_moves(flush_time, clear_history_time)
self.last_flush_time = flush_time
+
def _advance_move_time(self, next_print_time):
pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME
flush_time = max(self.last_flush_time, self.print_time - pt_delay)
@@ -324,6 +367,7 @@ class ToolHead:
self._advance_flush_time(flush_time)
if flush_time >= want_flush_time:
break
+
def _calc_print_time(self):
curtime = self.reactor.monotonic()
est_print_time = self.mcu.estimated_print_time(curtime)
@@ -332,8 +376,10 @@ class ToolHead:
min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time)
if min_print_time > self.print_time:
self.print_time = min_print_time
- self.printer.send_event("toolhead:sync_print_time",
- curtime, est_print_time, self.print_time)
+ self.printer.send_event(
+ "toolhead:sync_print_time", curtime, est_print_time, self.print_time
+ )
+
def _process_lookahead(self, lazy=False):
moves = self.lookahead.flush(lazy=lazy)
if not moves:
@@ -342,40 +388,55 @@ class ToolHead:
if self.special_queuing_state:
# Transition from "NeedPrime"/"Priming" state to main state
self.special_queuing_state = ""
- self.need_check_pause = -1.
+ self.need_check_pause = -1.0
self._calc_print_time()
# Queue moves into trapezoid motion queue (trapq)
next_move_time = self.print_time
for move in moves:
if move.is_kinematic_move:
self.trapq_append(
- self.trapq, next_move_time,
- move.accel_t, move.cruise_t, move.decel_t,
- move.start_pos[0], move.start_pos[1], move.start_pos[2],
- move.axes_r[0], move.axes_r[1], move.axes_r[2],
- move.start_v, move.cruise_v, move.accel)
+ self.trapq,
+ next_move_time,
+ move.accel_t,
+ move.cruise_t,
+ move.decel_t,
+ move.start_pos[0],
+ move.start_pos[1],
+ move.start_pos[2],
+ move.axes_r[0],
+ move.axes_r[1],
+ move.axes_r[2],
+ move.start_v,
+ move.cruise_v,
+ move.accel,
+ )
for e_index, ea in enumerate(self.extra_axes):
if move.axes_d[e_index + 3]:
ea.process_move(next_move_time, move, e_index + 3)
- next_move_time = (next_move_time + move.accel_t
- + move.cruise_t + move.decel_t)
+ next_move_time = (
+ next_move_time + move.accel_t + move.cruise_t + move.decel_t
+ )
for cb in move.timing_callbacks:
cb(next_move_time)
# Generate steps for moves
- self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay,
- set_step_gen_time=True)
+ self.note_mcu_movequeue_activity(
+ next_move_time + self.kin_flush_delay, set_step_gen_time=True
+ )
self._advance_move_time(next_move_time)
+
def _flush_lookahead(self):
# Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime"
self._process_lookahead()
self.special_queuing_state = "NeedPrime"
- self.need_check_pause = -1.
+ self.need_check_pause = -1.0
self.lookahead.set_flush_time(BUFFER_TIME_HIGH)
- self.check_stall_time = 0.
+ self.check_stall_time = 0.0
+
def flush_step_generation(self):
self._flush_lookahead()
self._advance_flush_time(self.step_gen_time)
self.min_restart_time = max(self.min_restart_time, self.print_time)
+
def get_last_move_time(self):
if self.special_queuing_state:
self._flush_lookahead()
@@ -383,6 +444,7 @@ class ToolHead:
else:
self._process_lookahead()
return self.print_time
+
def _check_pause(self):
eventtime = self.reactor.monotonic()
est_print_time = self.mcu.estimated_print_time(eventtime)
@@ -392,29 +454,29 @@ class ToolHead:
# Was in "NeedPrime" state and got there from idle input
if est_print_time < self.check_stall_time:
self.print_stall += 1
- self.check_stall_time = 0.
+ self.check_stall_time = 0.0
# Transition from "NeedPrime"/"Priming" state to "Priming" state
self.special_queuing_state = "Priming"
- self.need_check_pause = -1.
+ self.need_check_pause = -1.0
if self.priming_timer is None:
- self.priming_timer = self.reactor.register_timer(
- self._priming_handler)
+ self.priming_timer = self.reactor.register_timer(self._priming_handler)
wtime = eventtime + max(0.100, buffer_time - BUFFER_TIME_LOW)
self.reactor.update_timer(self.priming_timer, wtime)
# Check if there are lots of queued moves and pause if so
while 1:
pause_time = buffer_time - BUFFER_TIME_HIGH
- if pause_time <= 0.:
+ if pause_time <= 0.0:
break
if not self.can_pause:
self.need_check_pause = self.reactor.NEVER
return
- eventtime = self.reactor.pause(eventtime + min(1., pause_time))
+ eventtime = self.reactor.pause(eventtime + min(1.0, pause_time))
est_print_time = self.mcu.estimated_print_time(eventtime)
buffer_time = self.print_time - est_print_time
if not self.special_queuing_state:
# In main state - defer pause checking until needed
self.need_check_pause = est_print_time + BUFFER_TIME_HIGH + 0.100
+
def _priming_handler(self, eventtime):
self.reactor.unregister_timer(self.priming_timer)
self.priming_timer = None
@@ -426,6 +488,7 @@ class ToolHead:
logging.exception("Exception in priming_handler")
self.printer.invoke_shutdown("Exception in priming_handler")
return self.reactor.NEVER
+
def _flush_handler(self, eventtime):
try:
est_print_time = self.mcu.estimated_print_time(eventtime)
@@ -455,21 +518,26 @@ class ToolHead:
logging.exception("Exception in flush_handler")
self.printer.invoke_shutdown("Exception in flush_handler")
return self.reactor.NEVER
+
# Movement commands
def get_position(self):
return list(self.commanded_pos)
+
def set_position(self, newpos, homing_axes=""):
self.flush_step_generation()
ffi_main, ffi_lib = chelper.get_ffi()
- ffi_lib.trapq_set_position(self.trapq, self.print_time,
- newpos[0], newpos[1], newpos[2])
+ ffi_lib.trapq_set_position(
+ self.trapq, self.print_time, newpos[0], newpos[1], newpos[2]
+ )
self.commanded_pos[:3] = newpos[:3]
self.kin.set_position(newpos, homing_axes)
self.printer.send_event("toolhead:set_position")
+
def limit_next_junction_speed(self, speed):
last_move = self.lookahead.get_last()
if last_move is not None:
last_move.limit_next_junction_speed(speed)
+
def move(self, newpos, speed):
move = Move(self, self.commanded_pos, newpos, speed)
if not move.move_d:
@@ -485,6 +553,7 @@ class ToolHead:
self._process_lookahead(lazy=True)
if self.print_time > self.need_check_pause:
self._check_pause()
+
def manual_move(self, coord, speed):
curpos = list(self.commanded_pos)
for i in range(len(coord)):
@@ -492,18 +561,23 @@ class ToolHead:
curpos[i] = coord[i]
self.move(curpos, speed)
self.printer.send_event("toolhead:manual_move")
+
def dwell(self, delay):
- next_print_time = self.get_last_move_time() + max(0., delay)
+ next_print_time = self.get_last_move_time() + max(0.0, delay)
self._advance_move_time(next_print_time)
self._check_pause()
+
def wait_moves(self):
self._flush_lookahead()
eventtime = self.reactor.monotonic()
- while (not self.special_queuing_state
- or self.print_time >= self.mcu.estimated_print_time(eventtime)):
+ while (
+ not self.special_queuing_state
+ or self.print_time >= self.mcu.estimated_print_time(eventtime)
+ ):
if not self.can_pause:
break
eventtime = self.reactor.pause(eventtime + 0.100)
+
def set_extruder(self, extruder, extrude_pos):
# XXX - should use add_extra_axis
prev_ea_trapq = self.extra_axes[0].get_trapq()
@@ -514,8 +588,10 @@ class ToolHead:
ea_trapq = extruder.get_trapq()
if ea_trapq is not None:
self.flush_trapqs.append(ea_trapq)
+
def get_extruder(self):
return self.extra_axes[0]
+
def add_extra_axis(self, ea, axis_pos):
self._flush_lookahead()
self.extra_axes.append(ea)
@@ -524,6 +600,7 @@ class ToolHead:
if ea_trapq is not None:
self.flush_trapqs.append(ea_trapq)
self.printer.send_event("toolhead:update_extra_axes")
+
def remove_extra_axis(self, ea):
self._flush_lookahead()
if ea not in self.extra_axes:
@@ -535,8 +612,10 @@ class ToolHead:
self.commanded_pos.pop(ea_index)
self.extra_axes.pop(ea_index - 3)
self.printer.send_event("toolhead:update_extra_axes")
+
def get_extra_axes(self):
return [None, None, None] + self.extra_axes
+
# Homing "drip move" handling
def drip_update_time(self, next_print_time, drip_completion, addstepper=()):
# Transition from "NeedPrime"/"Priming"/main state to "Drip" state
@@ -545,7 +624,7 @@ class ToolHead:
self.reactor.update_timer(self.flush_timer, self.reactor.NEVER)
self.do_kick_flush_timer = False
self.lookahead.set_flush_time(BUFFER_TIME_HIGH)
- self.check_stall_time = 0.
+ self.check_stall_time = 0.0
# Update print_time in segments until drip_completion signal
flush_delay = DRIP_TIME + STEPCOMPRESS_FLUSH_TIME + self.kin_flush_delay
while self.print_time < next_print_time:
@@ -554,19 +633,21 @@ class ToolHead:
curtime = self.reactor.monotonic()
est_print_time = self.mcu.estimated_print_time(curtime)
wait_time = self.print_time - est_print_time - flush_delay
- if wait_time > 0. and self.can_pause:
+ if wait_time > 0.0 and self.can_pause:
# Pause before sending more steps
drip_completion.wait(curtime + wait_time)
continue
npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time)
- self.note_mcu_movequeue_activity(npt + self.kin_flush_delay,
- set_step_gen_time=True)
+ self.note_mcu_movequeue_activity(
+ npt + self.kin_flush_delay, set_step_gen_time=True
+ )
for stepper in addstepper:
stepper.generate_steps(npt)
self._advance_move_time(npt)
# Exit "Drip" state
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
self.flush_step_generation()
+
def _drip_load_trapq(self, submit_move):
# Queue move into trapezoid motion queue (trapq)
if submit_move.move_d:
@@ -577,15 +658,27 @@ class ToolHead:
next_move_time = self.print_time
for move in moves:
self.trapq_append(
- self.trapq, next_move_time,
- move.accel_t, move.cruise_t, move.decel_t,
- move.start_pos[0], move.start_pos[1], move.start_pos[2],
- move.axes_r[0], move.axes_r[1], move.axes_r[2],
- move.start_v, move.cruise_v, move.accel)
- next_move_time = (next_move_time + move.accel_t
- + move.cruise_t + move.decel_t)
+ self.trapq,
+ next_move_time,
+ move.accel_t,
+ move.cruise_t,
+ move.decel_t,
+ move.start_pos[0],
+ move.start_pos[1],
+ move.start_pos[2],
+ move.axes_r[0],
+ move.axes_r[1],
+ move.axes_r[2],
+ move.start_v,
+ move.cruise_v,
+ move.accel,
+ )
+ next_move_time = (
+ next_move_time + move.accel_t + move.cruise_t + move.decel_t
+ )
self.lookahead.reset()
return next_move_time
+
def drip_move(self, newpos, speed, drip_completion):
# Create and verify move is valid
newpos = newpos[:3] + self.commanded_pos[3:]
@@ -600,6 +693,7 @@ class ToolHead:
self.drip_update_time(next_move_time, drip_completion)
# Move finished; cleanup any remnants on trapq
self.trapq_finalize_moves(self.trapq, self.reactor.NEVER, 0)
+
# Misc commands
def stats(self, eventtime):
max_queue_time = max(self.print_time, self.last_flush_time)
@@ -608,43 +702,58 @@ class ToolHead:
est_print_time = self.mcu.estimated_print_time(eventtime)
self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE
buffer_time = self.print_time - est_print_time
- is_active = buffer_time > -60. or not self.special_queuing_state
+ is_active = buffer_time > -60.0 or not self.special_queuing_state
if self.special_queuing_state == "Drip":
- buffer_time = 0.
+ buffer_time = 0.0
return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
- self.print_time, max(buffer_time, 0.), self.print_stall)
+ self.print_time,
+ max(buffer_time, 0.0),
+ self.print_stall,
+ )
+
def check_busy(self, eventtime):
est_print_time = self.mcu.estimated_print_time(eventtime)
lookahead_empty = not self.lookahead.queue
return self.print_time, est_print_time, lookahead_empty
+
def get_status(self, eventtime):
print_time = self.print_time
estimated_print_time = self.mcu.estimated_print_time(eventtime)
extruder = self.extra_axes[0]
res = dict(self.kin.get_status(eventtime))
- res.update({ 'print_time': print_time,
- 'stalls': self.print_stall,
- 'estimated_print_time': estimated_print_time,
- 'extruder': extruder.get_name(),
- 'position': self.Coord(*self.commanded_pos[:4]),
- 'max_velocity': self.max_velocity,
- 'max_accel': self.max_accel,
- 'minimum_cruise_ratio': self.min_cruise_ratio,
- 'square_corner_velocity': self.square_corner_velocity})
+ res.update(
+ {
+ "print_time": print_time,
+ "stalls": self.print_stall,
+ "estimated_print_time": estimated_print_time,
+ "extruder": extruder.get_name(),
+ "position": self.Coord(*self.commanded_pos[:4]),
+ "max_velocity": self.max_velocity,
+ "max_accel": self.max_accel,
+ "minimum_cruise_ratio": self.min_cruise_ratio,
+ "square_corner_velocity": self.square_corner_velocity,
+ }
+ )
return res
+
def _handle_shutdown(self):
self.can_pause = False
self.lookahead.reset()
+
def get_kinematics(self):
return self.kin
+
def get_trapq(self):
return self.trapq
+
def register_step_generator(self, handler):
self.step_generators.append(handler)
+
def unregister_step_generator(self, handler):
if handler in self.step_generators:
self.step_generators.remove(handler)
- def note_step_generation_scan_time(self, delay, old_delay=0.):
+
+ def note_step_generation_scan_time(self, delay, old_delay=0.0):
self.flush_step_generation()
if old_delay:
self.kin_flush_times.pop(self.kin_flush_times.index(old_delay))
@@ -652,12 +761,14 @@ class ToolHead:
self.kin_flush_times.append(delay)
new_delay = max(self.kin_flush_times + [SDS_CHECK_TIME])
self.kin_flush_delay = new_delay
+
def register_lookahead_callback(self, callback):
last_move = self.lookahead.get_last()
if last_move is None:
callback(self.get_last_move_time())
return
last_move.timing_callbacks.append(callback)
+
def note_mcu_movequeue_activity(self, mq_time, set_step_gen_time=False):
self.need_flush_time = max(self.need_flush_time, mq_time)
if set_step_gen_time:
@@ -665,35 +776,41 @@ class ToolHead:
if self.do_kick_flush_timer:
self.do_kick_flush_timer = False
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
+
def get_max_velocity(self):
return self.max_velocity, self.max_accel
+
def _calc_junction_deviation(self):
scv2 = self.square_corner_velocity**2
- self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel
- self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio)
+ self.junction_deviation = scv2 * (math.sqrt(2.0) - 1.0) / self.max_accel
+ self.max_accel_to_decel = self.max_accel * (1.0 - self.min_cruise_ratio)
+
def cmd_G4(self, gcmd):
# Dwell
- delay = gcmd.get_float('P', 0., minval=0.) / 1000.
+ delay = gcmd.get_float("P", 0.0, minval=0.0) / 1000.0
self.dwell(delay)
+
def cmd_M400(self, gcmd):
# Wait for current moves to finish
self.wait_moves()
+
cmd_SET_VELOCITY_LIMIT_help = "Set printer velocity limits"
+
def cmd_SET_VELOCITY_LIMIT(self, gcmd):
- max_velocity = gcmd.get_float('VELOCITY', None, above=0.)
- max_accel = gcmd.get_float('ACCEL', None, above=0.)
+ max_velocity = gcmd.get_float("VELOCITY", None, above=0.0)
+ max_accel = gcmd.get_float("ACCEL", None, above=0.0)
square_corner_velocity = gcmd.get_float(
- 'SQUARE_CORNER_VELOCITY', None, minval=0.)
+ "SQUARE_CORNER_VELOCITY", None, minval=0.0
+ )
min_cruise_ratio = gcmd.get_float(
- 'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.)
+ "MINIMUM_CRUISE_RATIO", None, minval=0.0, below=1.0
+ )
if min_cruise_ratio is None:
- req_accel_to_decel = gcmd.get_float('ACCEL_TO_DECEL',
- None, above=0.)
+ req_accel_to_decel = gcmd.get_float("ACCEL_TO_DECEL", None, above=0.0)
if req_accel_to_decel is not None and max_accel is not None:
- min_cruise_ratio = 1. - min(1., req_accel_to_decel / max_accel)
+ min_cruise_ratio = 1.0 - min(1.0, req_accel_to_decel / max_accel)
elif req_accel_to_decel is not None and max_accel is None:
- min_cruise_ratio = 1. - min(1., (req_accel_to_decel
- / self.max_accel))
+ min_cruise_ratio = 1.0 - min(1.0, (req_accel_to_decel / self.max_accel))
if max_velocity is not None:
self.max_velocity = max_velocity
if max_accel is not None:
@@ -703,31 +820,44 @@ class ToolHead:
if min_cruise_ratio is not None:
self.min_cruise_ratio = min_cruise_ratio
self._calc_junction_deviation()
- msg = ("max_velocity: %.6f\n"
- "max_accel: %.6f\n"
- "minimum_cruise_ratio: %.6f\n"
- "square_corner_velocity: %.6f" % (
- self.max_velocity, self.max_accel,
- self.min_cruise_ratio, self.square_corner_velocity))
+ msg = (
+ "max_velocity: %.6f\n"
+ "max_accel: %.6f\n"
+ "minimum_cruise_ratio: %.6f\n"
+ "square_corner_velocity: %.6f"
+ % (
+ self.max_velocity,
+ self.max_accel,
+ self.min_cruise_ratio,
+ self.square_corner_velocity,
+ )
+ )
self.printer.set_rollover_info("toolhead", "toolhead: %s" % (msg,))
- if (max_velocity is None and max_accel is None
- and square_corner_velocity is None and min_cruise_ratio is None):
+ if (
+ max_velocity is None
+ and max_accel is None
+ and square_corner_velocity is None
+ and min_cruise_ratio is None
+ ):
gcmd.respond_info(msg, log=False)
+
def cmd_M204(self, gcmd):
# Use S for accel
- accel = gcmd.get_float('S', None, above=0.)
+ accel = gcmd.get_float("S", None, above=0.0)
if accel is None:
# Use minimum of P and T for accel
- p = gcmd.get_float('P', None, above=0.)
- t = gcmd.get_float('T', None, above=0.)
+ p = gcmd.get_float("P", None, above=0.0)
+ t = gcmd.get_float("T", None, above=0.0)
if p is None or t is None:
- gcmd.respond_info('Invalid M204 command "%s"'
- % (gcmd.get_commandline(),))
+ gcmd.respond_info(
+ 'Invalid M204 command "%s"' % (gcmd.get_commandline(),)
+ )
return
accel = min(p, t)
self.max_accel = accel
self._calc_junction_deviation()
+
def add_printer_objects(config):
- config.get_printer().add_object('toolhead', ToolHead(config))
+ config.get_printer().add_object("toolhead", ToolHead(config))
kinematics.extruder.add_printer_objects(config)
diff --git a/klippy/util.py b/klippy/util.py
index 6a8baee7..43a2becc 100644
--- a/klippy/util.py
+++ b/klippy/util.py
@@ -11,15 +11,19 @@ import subprocess, traceback, shlex
# Low-level Unix commands
######################################################################
+
# Return the SIGINT interrupt handler back to the OS default
def fix_sigint():
signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+
fix_sigint()
+
# Set a file-descriptor as non-blocking
def set_nonblock(fd):
- fcntl.fcntl(fd, fcntl.F_SETFL
- , fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+
# Clear HUPCL flag
def clear_hupcl(fd):
@@ -30,6 +34,7 @@ def clear_hupcl(fd):
except termios.error:
pass
+
# Support for creating a pseudo-tty for emulating a serial port
def create_pty(ptyname):
mfd, sfd = pty.openpty()
@@ -51,6 +56,7 @@ def create_pty(ptyname):
# Helper code for extracting mcu build info
######################################################################
+
def dump_file_stats(build_dir, filename):
fname = os.path.join(build_dir, filename)
try:
@@ -61,48 +67,55 @@ def dump_file_stats(build_dir, filename):
except:
logging.info("No build file %s", fname)
+
# Try to log information on the last mcu build
def dump_mcu_build():
- build_dir = os.path.join(os.path.dirname(__file__), '..')
+ build_dir = os.path.join(os.path.dirname(__file__), "..")
# Try to log last mcu config
- dump_file_stats(build_dir, '.config')
+ dump_file_stats(build_dir, ".config")
try:
- f = open(os.path.join(build_dir, '.config'), 'r')
- data = f.read(32*1024)
+ f = open(os.path.join(build_dir, ".config"), "r")
+ data = f.read(32 * 1024)
f.close()
- logging.info("========= Last MCU build config =========\n%s"
- "=======================", data)
+ logging.info(
+ "========= Last MCU build config =========\n%s" "=======================",
+ data,
+ )
except:
pass
# Try to log last mcu build version
- dump_file_stats(build_dir, 'out/klipper.dict')
+ dump_file_stats(build_dir, "out/klipper.dict")
try:
- f = open(os.path.join(build_dir, 'out/klipper.dict'), 'r')
- data = f.read(32*1024)
+ f = open(os.path.join(build_dir, "out/klipper.dict"), "r")
+ data = f.read(32 * 1024)
f.close()
data = json.loads(data)
- logging.info("Last MCU build version: %s", data.get('version', ''))
- logging.info("Last MCU build tools: %s", data.get('build_versions', ''))
- cparts = ["%s=%s" % (k, v) for k, v in data.get('config', {}).items()]
+ logging.info("Last MCU build version: %s", data.get("version", ""))
+ logging.info("Last MCU build tools: %s", data.get("build_versions", ""))
+ cparts = ["%s=%s" % (k, v) for k, v in data.get("config", {}).items()]
logging.info("Last MCU build config: %s", " ".join(cparts))
except:
pass
- dump_file_stats(build_dir, 'out/klipper.elf')
+ dump_file_stats(build_dir, "out/klipper.elf")
######################################################################
# Python2 wrapper hacks
######################################################################
+
def setup_python2_wrappers():
if sys.version_info.major >= 3:
return
# Add module hacks so that common Python3 module imports work in Python2
import ConfigParser, Queue, io, StringIO, time
+
sys.modules["configparser"] = ConfigParser
sys.modules["queue"] = Queue
io.StringIO = StringIO.StringIO
time.process_time = time.clock
+
+
setup_python2_wrappers()
@@ -110,35 +123,38 @@ setup_python2_wrappers()
# General system and software information
######################################################################
+
def get_cpu_info():
try:
- f = open('/proc/cpuinfo', 'r')
+ f = open("/proc/cpuinfo", "r")
data = f.read()
f.close()
except (IOError, OSError) as e:
- logging.debug("Exception on read /proc/cpuinfo: %s",
- traceback.format_exc())
+ logging.debug("Exception on read /proc/cpuinfo: %s", traceback.format_exc())
return "?"
- lines = [l.split(':', 1) for l in data.split('\n')]
+ lines = [l.split(":", 1) for l in data.split("\n")]
lines = [(l[0].strip(), l[1].strip()) for l in lines if len(l) == 2]
core_count = [k for k, v in lines].count("processor")
model_name = dict(lines).get("model name", "?")
return "%d core %s" % (core_count, model_name)
+
def get_version_from_file(klippy_src):
try:
- with open(os.path.join(klippy_src, '.version')) as h:
+ with open(os.path.join(klippy_src, ".version")) as h:
return h.read().rstrip()
except IOError:
pass
return "?"
+
def _get_repo_info(gitdir):
repo_info = {"branch": "?", "remote": "?", "url": "?"}
- prog_branch = ('git', '-C', gitdir, 'branch', '--no-color')
+ prog_branch = ("git", "-C", gitdir, "branch", "--no-color")
try:
- process = subprocess.Popen(prog_branch, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ process = subprocess.Popen(
+ prog_branch, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
branch_list, err = process.communicate()
retcode = process.wait()
if retcode != 0:
@@ -159,9 +175,10 @@ def _get_repo_info(gitdir):
repo_info["remote"] = parts[0]
else:
key = "branch.%s.remote" % (repo_info["branch"],)
- prog_config = ('git', '-C', gitdir, 'config', '--get', key)
- process = subprocess.Popen(prog_config, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ prog_config = ("git", "-C", gitdir, "config", "--get", key)
+ process = subprocess.Popen(
+ prog_config, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
remote_info, err = process.communicate()
retcode = process.wait()
if retcode != 0:
@@ -169,9 +186,16 @@ def _get_repo_info(gitdir):
return repo_info
repo_info["remote"] = str(remote_info.strip().decode())
prog_remote_url = (
- 'git', '-C', gitdir, 'remote', 'get-url', repo_info["remote"])
- process = subprocess.Popen(prog_remote_url, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ "git",
+ "-C",
+ gitdir,
+ "remote",
+ "get-url",
+ repo_info["remote"],
+ )
+ process = subprocess.Popen(
+ prog_remote_url, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
remote_url, err = process.communicate()
retcode = process.wait()
if retcode != 0:
@@ -182,33 +206,43 @@ def _get_repo_info(gitdir):
logging.debug("Error fetching repo info: %s", traceback.format_exc())
return repo_info
+
def get_git_version(from_file=True):
git_info = {
"version": "?",
"file_status": [],
"branch": "?",
"remote": "?",
- "url": "?"
+ "url": "?",
}
klippy_src = os.path.dirname(__file__)
# Obtain version info from "git" program
- gitdir = os.path.join(klippy_src, '..')
- prog_desc = ('git', '-C', gitdir, 'describe', '--always',
- '--tags', '--long', '--dirty')
- prog_status = ('git', '-C', gitdir, 'status', '--porcelain', '--ignored')
+ gitdir = os.path.join(klippy_src, "..")
+ prog_desc = (
+ "git",
+ "-C",
+ gitdir,
+ "describe",
+ "--always",
+ "--tags",
+ "--long",
+ "--dirty",
+ )
+ prog_status = ("git", "-C", gitdir, "status", "--porcelain", "--ignored")
try:
- process = subprocess.Popen(prog_desc, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ process = subprocess.Popen(
+ prog_desc, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
ver, err = process.communicate()
retcode = process.wait()
if retcode == 0:
git_info["version"] = str(ver.strip().decode())
- process = subprocess.Popen(prog_status, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ process = subprocess.Popen(
+ prog_status, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
stat, err = process.communicate()
- status = [l.split(None, 1)
- for l in str(stat.strip().decode()).split('\n')]
+ status = [l.split(None, 1) for l in str(stat.strip().decode()).split("\n")]
retcode = process.wait()
if retcode == 0:
git_info["file_status"] = status
diff --git a/klippy/webhooks.py b/klippy/webhooks.py
index 9e17177a..1a471a93 100644
--- a/klippy/webhooks.py
+++ b/klippy/webhooks.py
@@ -19,47 +19,58 @@ except ImportError:
#
json_loads_byteify = None
if sys.version_info.major < 3:
+
def json_loads_byteify(data, ignore_dicts=False):
if isinstance(data, unicode):
- return data.encode('utf-8')
+ return data.encode("utf-8")
if isinstance(data, list):
return [json_loads_byteify(i, True) for i in data]
if isinstance(data, dict) and not ignore_dicts:
- return {json_loads_byteify(k, True): json_loads_byteify(v, True)
- for k, v in data.items()}
+ return {
+ json_loads_byteify(k, True): json_loads_byteify(v, True)
+ for k, v in data.items()
+ }
return data
+
def json_dumps(obj):
- return json.dumps(obj, separators=(',', ':')).encode()
+ return json.dumps(obj, separators=(",", ":")).encode()
+
def json_loads(data):
return json.loads(data, object_hook=json_loads_byteify)
+
else:
json_dumps = msgspec.json.encode
json_loads = msgspec.json.decode
REQUEST_LOG_SIZE = 20
+
class WebRequestError(gcode.CommandError):
- def __init__(self, message,):
+ def __init__(
+ self,
+ message,
+ ):
Exception.__init__(self, message)
def to_dict(self):
- return {
- 'error': 'WebRequestError',
- 'message': str(self)}
+ return {"error": "WebRequestError", "message": str(self)}
+
class Sentinel:
pass
+
class WebRequest:
error = WebRequestError
+
def __init__(self, client_conn, request):
self.client_conn = client_conn
base_request = json_loads(request)
if type(base_request) != dict:
raise ValueError("Not a top-level dictionary")
- self.id = base_request.get('id', None)
- self.method = base_request.get('method')
- self.params = base_request.get('params', {})
+ self.id = base_request.get("id", None)
+ self.method = base_request.get("method")
+ self.params = base_request.get("params", {})
if type(self.method) != str or type(self.params) != dict:
raise ValueError("Invalid request type")
self.response = None
@@ -72,8 +83,7 @@ class WebRequest:
value = self.params.get(item, default)
if value is Sentinel:
raise WebRequestError("Missing Argument [%s]" % (item,))
- if (types is not None and type(value) not in types
- and item in self.params):
+ if types is not None and type(value) not in types and item in self.params:
raise WebRequestError("Invalid Argument Type [%s]" % (item,))
return value
@@ -113,6 +123,7 @@ class WebRequest:
self.response = {}
return {"id": self.id, rtype: self.response}
+
class ServerSocket:
def __init__(self, webhooks, printer):
self.printer = printer
@@ -121,8 +132,8 @@ class ServerSocket:
self.sock = self.fd_handle = None
self.clients = {}
start_args = printer.get_start_args()
- server_address = start_args.get('apiserver')
- is_fileinput = (start_args.get('debuginput') is not None)
+ server_address = start_args.get("apiserver")
+ is_fileinput = start_args.get("debuginput") is not None
if not server_address or is_fileinput:
# Do not enable server
return
@@ -132,11 +143,10 @@ class ServerSocket:
self.sock.bind(server_address)
self.sock.listen(1)
self.fd_handle = self.reactor.register_fd(
- self.sock.fileno(), self._handle_accept)
- printer.register_event_handler(
- 'klippy:disconnect', self._handle_disconnect)
- printer.register_event_handler(
- "klippy:shutdown", self._handle_shutdown)
+ self.sock.fileno(), self._handle_accept
+ )
+ printer.register_event_handler("klippy:disconnect", self._handle_disconnect)
+ printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
def _handle_accept(self, eventtime):
try:
@@ -167,8 +177,8 @@ class ServerSocket:
except OSError:
if os.path.exists(file_path):
logging.exception(
- "webhooks: Unable to delete socket file '%s'"
- % (file_path))
+ "webhooks: Unable to delete socket file '%s'" % (file_path)
+ )
raise
def pop_client(self, client_id):
@@ -184,6 +194,7 @@ class ServerSocket:
client.close()
return False, ""
+
class ClientConnection:
def __init__(self, server, sock):
self.printer = server.printer
@@ -193,7 +204,8 @@ class ClientConnection:
self.uid = id(self)
self.sock = sock
self.fd_handle = self.reactor.register_fd(
- self.sock.fileno(), self.process_received, self._do_send)
+ self.sock.fileno(), self.process_received, self._do_send
+ )
self.partial_data = self.send_buffer = b""
self.is_blocking = False
self.blocking_count = 0
@@ -202,8 +214,13 @@ class ClientConnection:
def dump_request_log(self):
out = []
- out.append("Dumping %d requests for client %d"
- % (len(self.request_log), self.uid,))
+ out.append(
+ "Dumping %d requests for client %d"
+ % (
+ len(self.request_log),
+ self.uid,
+ )
+ )
for eventtime, request in self.request_log:
out.append("Received %f: %s" % (eventtime, request))
logging.info("\n".join(out))
@@ -248,7 +265,7 @@ class ClientConnection:
# Socket Closed
self.close()
return
- requests = data.split(b'\x03')
+ requests = data.split(b"\x03")
requests[0] = self.partial_data + requests[0]
self.partial_data = requests.pop()
for req in requests:
@@ -256,11 +273,11 @@ class ClientConnection:
try:
web_request = WebRequest(self, req)
except Exception:
- logging.exception("webhooks: Error decoding Server Request %s"
- % (req))
+ logging.exception("webhooks: Error decoding Server Request %s" % (req))
continue
self.reactor.register_callback(
- lambda e, s=self, wr=web_request: s._process_request(wr))
+ lambda e, s=self, wr=web_request: s._process_request(wr)
+ )
def _process_request(self, web_request):
try:
@@ -269,8 +286,7 @@ class ClientConnection:
except self.printer.command_error as e:
web_request.set_error(WebRequestError(str(e)))
except Exception as e:
- msg = ("Internal Error on WebRequest: %s"
- % (web_request.get_method()))
+ msg = "Internal Error on WebRequest: %s" % (web_request.get_method())
logging.exception(msg)
web_request.set_error(WebRequestError(str(e)))
self.printer.invoke_shutdown(msg)
@@ -284,7 +300,7 @@ class ClientConnection:
jmsg = json_dumps(data)
self.send_buffer += jmsg + b"\x03"
except (TypeError, ValueError) as e:
- msg = ("json encoding error: %s" % (str(e),))
+ msg = "json encoding error: %s" % (str(e),)
logging.exception(msg)
self.printer.invoke_shutdown(msg)
return
@@ -312,6 +328,7 @@ class ClientConnection:
self.is_blocking = False
self.send_buffer = self.send_buffer[sent:]
+
class WebHooks:
def __init__(self, printer):
self.printer = printer
@@ -320,8 +337,7 @@ class WebHooks:
self._mux_endpoints = {}
self.register_endpoint("info", self._handle_info_request)
self.register_endpoint("emergency_stop", self._handle_estop_request)
- self.register_endpoint("register_remote_method",
- self._handle_rpc_registration)
+ self.register_endpoint("register_remote_method", self._handle_rpc_registration)
self.sconn = ServerSocket(self, printer)
def register_endpoint(self, path, callback):
@@ -338,11 +354,13 @@ class WebHooks:
if prev_key != key:
raise self.printer.config_error(
"mux endpoint %s %s %s may have only one key (%s)"
- % (path, key, value, prev_key))
+ % (path, key, value, prev_key)
+ )
if value in prev_values:
raise self.printer.config_error(
"mux endpoint %s %s %s already registered (%s)"
- % (path, key, value, prev_values))
+ % (path, key, value, prev_values)
+ )
prev_values[value] = callback
def _handle_mux(self, web_request):
@@ -352,30 +370,33 @@ class WebHooks:
else:
key_param = web_request.get(key)
if key_param not in values:
- raise web_request.error("The value '%s' is not valid for %s"
- % (key_param, key))
+ raise web_request.error(
+ "The value '%s' is not valid for %s" % (key_param, key)
+ )
values[key_param](web_request)
def _handle_list_endpoints(self, web_request):
- web_request.send({'endpoints': list(self._endpoints.keys())})
+ web_request.send({"endpoints": list(self._endpoints.keys())})
def _handle_info_request(self, web_request):
- client_info = web_request.get_dict('client_info', None)
+ client_info = web_request.get_dict("client_info", None)
if client_info is not None:
web_request.get_client_connection().set_client_info(client_info)
state_message, state = self.printer.get_state_message()
src_path = os.path.dirname(__file__)
klipper_path = os.path.normpath(os.path.join(src_path, ".."))
- response = {'state': state,
- 'state_message': state_message,
- 'hostname': socket.gethostname(),
- 'klipper_path': klipper_path,
- 'python_path': sys.executable,
- 'process_id': os.getpid(),
- 'user_id': os.getuid(),
- 'group_id': os.getgid()}
+ response = {
+ "state": state,
+ "state_message": state_message,
+ "hostname": socket.gethostname(),
+ "klipper_path": klipper_path,
+ "python_path": sys.executable,
+ "process_id": os.getpid(),
+ "user_id": os.getuid(),
+ "group_id": os.getgid(),
+ }
start_args = self.printer.get_start_args()
- for sa in ['log_file', 'config_file', 'software_version', 'cpu_info']:
+ for sa in ["log_file", "config_file", "software_version", "cpu_info"]:
response[sa] = start_args.get(sa)
web_request.send(response)
@@ -383,11 +404,13 @@ class WebHooks:
self.printer.invoke_shutdown("Shutdown due to webhooks request")
def _handle_rpc_registration(self, web_request):
- template = web_request.get_dict('response_template')
- method = web_request.get_str('remote_method')
+ template = web_request.get_dict("response_template")
+ method = web_request.get_str("remote_method")
new_conn = web_request.get_client_connection()
- logging.info("webhooks: registering remote method '%s' "
- "for connection id: %d" % (method, id(new_conn)))
+ logging.info(
+ "webhooks: registering remote method '%s' "
+ "for connection id: %d" % (method, id(new_conn))
+ )
self._remote_methods.setdefault(method, {})[new_conn] = template
def get_connection(self):
@@ -403,7 +426,7 @@ class WebHooks:
def get_status(self, eventtime):
state_message, state = self.printer.get_state_message()
- return {'state': state, 'state_message': state_message}
+ return {"state": state, "state_message": state_message}
def stats(self, eventtime):
return self.sconn.stats(eventtime)
@@ -411,21 +434,24 @@ class WebHooks:
def call_remote_method(self, method, **kwargs):
if method not in self._remote_methods:
raise self.printer.command_error(
- "Remote method '%s' not registered" % (method))
+ "Remote method '%s' not registered" % (method)
+ )
conn_map = self._remote_methods[method]
valid_conns = {}
for conn, template in conn_map.items():
if not conn.is_closed():
valid_conns[conn] = template
- out = {'params': kwargs}
+ out = {"params": kwargs}
out.update(template)
conn.send(out)
if not valid_conns:
del self._remote_methods[method]
raise self.printer.command_error(
- "No active connections for method '%s'" % (method))
+ "No active connections for method '%s'" % (method)
+ )
self._remote_methods[method] = valid_conns
+
class GCodeHelper:
def __init__(self, printer):
self.printer = printer
@@ -434,39 +460,45 @@ class GCodeHelper:
self.is_output_registered = False
self.clients = {}
# Register webhooks
- wh = printer.lookup_object('webhooks')
+ wh = printer.lookup_object("webhooks")
wh.register_endpoint("gcode/help", self._handle_help)
wh.register_endpoint("gcode/script", self._handle_script)
wh.register_endpoint("gcode/restart", self._handle_restart)
- wh.register_endpoint("gcode/firmware_restart",
- self._handle_firmware_restart)
- wh.register_endpoint("gcode/subscribe_output",
- self._handle_subscribe_output)
+ wh.register_endpoint("gcode/firmware_restart", self._handle_firmware_restart)
+ wh.register_endpoint("gcode/subscribe_output", self._handle_subscribe_output)
+
def _handle_help(self, web_request):
web_request.send(self.gcode.get_command_help())
+
def _handle_script(self, web_request):
- self.gcode.run_script(web_request.get_str('script'))
+ self.gcode.run_script(web_request.get_str("script"))
+
def _handle_restart(self, web_request):
- self.gcode.run_script('restart')
+ self.gcode.run_script("restart")
+
def _handle_firmware_restart(self, web_request):
- self.gcode.run_script('firmware_restart')
+ self.gcode.run_script("firmware_restart")
+
def _output_callback(self, msg):
for cconn, template in list(self.clients.items()):
if cconn.is_closed():
del self.clients[cconn]
continue
tmp = dict(template)
- tmp['params'] = {'response': msg}
+ tmp["params"] = {"response": msg}
cconn.send(tmp)
+
def _handle_subscribe_output(self, web_request):
cconn = web_request.get_client_connection()
- template = web_request.get_dict('response_template', {})
+ template = web_request.get_dict("response_template", {})
self.clients[cconn] = template
if not self.is_output_registered:
self.gcode.register_output_handler(self._output_callback)
self.is_output_registered = True
-SUBSCRIPTION_REFRESH_TIME = .25
+
+SUBSCRIPTION_REFRESH_TIME = 0.25
+
class QueryStatusHelper:
def __init__(self, printer):
@@ -476,14 +508,17 @@ class QueryStatusHelper:
self.query_timer = None
self.last_query = {}
# Register webhooks
- webhooks = printer.lookup_object('webhooks')
+ webhooks = printer.lookup_object("webhooks")
webhooks.register_endpoint("objects/list", self._handle_list)
webhooks.register_endpoint("objects/query", self._handle_query)
webhooks.register_endpoint("objects/subscribe", self._handle_subscribe)
+
def _handle_list(self, web_request):
- objects = [n for n, o in self.printer.lookup_objects()
- if hasattr(o, 'get_status')]
- web_request.send({'objects': objects})
+ objects = [
+ n for n, o in self.printer.lookup_objects() if hasattr(o, "get_status")
+ ]
+ web_request.send({"objects": objects})
+
def _do_query(self, eventtime):
last_query = self.last_query
query = self.last_query = {}
@@ -502,7 +537,7 @@ class QueryStatusHelper:
res = query.get(obj_name, None)
if res is None:
po = self.printer.lookup_object(obj_name, None)
- if po is None or not hasattr(po, 'get_status'):
+ if po is None or not hasattr(po, "get_status"):
res = query[obj_name] = {}
else:
res = query[obj_name] = po.get_status(eventtime)
@@ -521,7 +556,7 @@ class QueryStatusHelper:
# Send data
if cquery or is_query:
tmp = dict(template)
- tmp['params'] = {'eventtime': eventtime, 'status': cquery}
+ tmp["params"] = {"eventtime": eventtime, "status": cquery}
send_func(tmp)
if not query:
# Unregister timer if there are no longer any subscriptions
@@ -530,8 +565,9 @@ class QueryStatusHelper:
self.query_timer = None
return reactor.NEVER
return eventtime + SUBSCRIPTION_REFRESH_TIME
+
def _handle_query(self, web_request, is_subscribe=False):
- objects = web_request.get_dict('objects')
+ objects = web_request.get_dict("objects")
# Validate subscription format
for k, v in objects.items():
if type(k) != str or (v is not None and type(v) != list):
@@ -542,7 +578,7 @@ class QueryStatusHelper:
raise web_request.error("Invalid argument")
# Add to pending queries
cconn = web_request.get_client_connection()
- template = web_request.get_dict('response_template', {})
+ template = web_request.get_dict("response_template", {})
if is_subscribe and cconn in self.clients:
del self.clients[cconn]
reactor = self.printer.get_reactor()
@@ -554,13 +590,15 @@ class QueryStatusHelper:
self.query_timer = qt
# Wait for data to be queried
msg = complete.wait()
- web_request.send(msg['params'])
+ web_request.send(msg["params"])
if is_subscribe:
self.clients[cconn] = (cconn, objects, cconn.send, template)
+
def _handle_subscribe(self, web_request):
self._handle_query(web_request, is_subscribe=True)
+
def add_early_printer_objects(printer):
- printer.add_object('webhooks', WebHooks(printer))
+ printer.add_object("webhooks", WebHooks(printer))
GCodeHelper(printer)
QueryStatusHelper(printer)
diff --git a/scripts/avrsim.py b/scripts/avrsim.py
index e7f191e8..0dd86200 100755
--- a/scripts/avrsim.py
+++ b/scripts/avrsim.py
@@ -7,9 +7,10 @@
import sys, optparse, time, os, pty, fcntl, termios, errno
import pysimulavr
-SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
+SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
SIMULAVR_FREQ = 10**9
+
# Class to read serial data from AVR serial transmit pin.
class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
def __init__(self, baud, terminal):
@@ -20,12 +21,14 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
self.delay = SIMULAVR_FREQ // baud
self.current = 0
self.pos = -1
+
def SetInState(self, pin):
pysimulavr.Pin.SetInState(self, pin)
self.state = pin.outState
if self.pos < 0 and pin.outState == pin.LOW:
self.pos = 0
self.sc.Add(self)
+
def DoStep(self, trueHwStep):
ishigh = self.state == self.HIGH
self.current |= ishigh << self.pos
@@ -33,26 +36,28 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
if self.pos == 1:
return int(self.delay * 1.5)
if self.pos >= SERIALBITS:
- data = bytearray([(self.current >> 1) & 0xff])
+ data = bytearray([(self.current >> 1) & 0xFF])
self.terminal.write(data)
self.pos = -1
self.current = 0
return -1
return self.delay
+
# Class to send serial data to AVR serial receive pin.
class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
def __init__(self, baud, terminal):
pysimulavr.Pin.__init__(self)
pysimulavr.PySimulationMember.__init__(self)
self.terminal = terminal
- self.SetPin('H')
+ self.SetPin("H")
self.sc = pysimulavr.SystemClock.Instance()
self.delay = SIMULAVR_FREQ // baud
self.current = 0
self.pos = 0
self.queue = bytearray()
self.sc.Add(self)
+
def DoStep(self, trueHwStep):
if not self.pos:
if not self.queue:
@@ -61,15 +66,16 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
return self.delay * 100
self.queue.extend(data)
self.current = (self.queue.pop(0) << 1) | 0x200
- newstate = 'L'
+ newstate = "L"
if self.current & (1 << self.pos):
- newstate = 'H'
+ newstate = "H"
self.SetPin(newstate)
self.pos += 1
if self.pos >= SERIALBITS:
self.pos = 0
return self.delay
+
# Support for creating VCD trace files
class Tracing:
def __init__(self, filename, signals):
@@ -80,36 +86,42 @@ class Tracing:
return
self.dman = pysimulavr.DumpManager.Instance()
self.dman.SetSingleDeviceApp()
+
def show_help(self):
ostr = pysimulavr.ostringstream()
self.dman.save(ostr)
sys.stdout.write(ostr.str())
sys.exit(1)
+
def load_options(self):
if self.dman is None:
return
- if self.signals.strip() == '?':
+ if self.signals.strip() == "?":
self.show_help()
- sigs = "\n".join(["+ " + s for s in self.signals.split(',')])
+ sigs = "\n".join(["+ " + s for s in self.signals.split(",")])
self.dman.addDumpVCD(self.filename, sigs, "ns", False, False)
+
def start(self):
if self.dman is not None:
self.dman.start()
+
def finish(self):
if self.dman is not None:
self.dman.stopApplication()
+
# Pace the simulation scaled to real time
class Pacing(pysimulavr.PySimulationMember):
def __init__(self, rate):
pysimulavr.PySimulationMember.__init__(self)
self.sc = pysimulavr.SystemClock.Instance()
- self.pacing_rate = 1. / (rate * SIMULAVR_FREQ)
+ self.pacing_rate = 1.0 / (rate * SIMULAVR_FREQ)
self.next_check_clock = 0
self.rel_time = time.time()
- self.best_offset = 0.
+ self.best_offset = 0.0
self.delay = SIMULAVR_FREQ // 10000
self.sc.Add(self)
+
def DoStep(self, trueHwStep):
curtime = time.time()
clock = self.sc.GetCurrentTime()
@@ -118,19 +130,23 @@ class Pacing(pysimulavr.PySimulationMember):
if offset > 0.000050:
time.sleep(offset - 0.000040)
if clock >= self.next_check_clock:
- self.rel_time -= min(self.best_offset, 0.)
+ self.rel_time -= min(self.best_offset, 0.0)
self.next_check_clock = clock + self.delay * 500
- self.best_offset = -999999999.
+ self.best_offset = -999999999.0
return self.delay
+
# Forward data from a terminal device to the serial port pins
class TerminalIO:
def __init__(self):
self.fd = -1
+
def run(self, fd):
self.fd = fd
+
def write(self, data):
os.write(self.fd, data)
+
def read(self):
try:
return os.read(self.fd, 64)
@@ -139,6 +155,7 @@ class TerminalIO:
pysimulavr.SystemClock.Instance().stop()
return ""
+
# Support for creating a pseudo-tty for emulating a serial port
def create_pty(ptyname):
mfd, sfd = pty.openpty()
@@ -147,16 +164,22 @@ def create_pty(ptyname):
except os.error:
pass
os.symlink(os.ttyname(sfd), ptyname)
- fcntl.fcntl(mfd, fcntl.F_SETFL
- , fcntl.fcntl(mfd, fcntl.F_GETFL) | os.O_NONBLOCK)
+ fcntl.fcntl(mfd, fcntl.F_SETFL, fcntl.fcntl(mfd, fcntl.F_GETFL) | os.O_NONBLOCK)
tcattr = termios.tcgetattr(mfd)
tcattr[0] &= ~(
- termios.IGNBRK | termios.BRKINT | termios.PARMRK | termios.ISTRIP |
- termios.INLCR | termios.IGNCR | termios.ICRNL | termios.IXON)
+ termios.IGNBRK
+ | termios.BRKINT
+ | termios.PARMRK
+ | termios.ISTRIP
+ | termios.INLCR
+ | termios.IGNCR
+ | termios.ICRNL
+ | termios.IXON
+ )
tcattr[1] &= ~termios.OPOST
tcattr[3] &= ~(
- termios.ECHO | termios.ECHONL | termios.ICANON | termios.ISIG |
- termios.IEXTEN)
+ termios.ECHO | termios.ECHONL | termios.ICANON | termios.ISIG | termios.IEXTEN
+ )
tcattr[2] &= ~(termios.CSIZE | termios.PARENB)
tcattr[2] |= termios.CS8
tcattr[6][termios.VMIN] = 0
@@ -164,25 +187,66 @@ def create_pty(ptyname):
termios.tcsetattr(mfd, termios.TCSAFLUSH, tcattr)
return mfd
+
def main():
usage = "%prog [options] <program.elf>"
opts = optparse.OptionParser(usage)
- opts.add_option("-m", "--machine", type="string", dest="machine",
- default="atmega644", help="type of AVR machine to simulate")
- opts.add_option("-s", "--speed", type="int", dest="speed", default=16000000,
- help="machine speed")
- opts.add_option("-r", "--rate", type="float", dest="pacing_rate",
- default=0., help="real-time pacing rate")
- opts.add_option("-b", "--baud", type="int", dest="baud", default=250000,
- help="baud rate of the emulated serial port")
- opts.add_option("-t", "--trace", type="string", dest="trace",
- help="signals to trace (? for help)")
- opts.add_option("-p", "--port", type="string", dest="port",
- default="/tmp/pseudoserial",
- help="pseudo-tty device to create for serial port")
+ opts.add_option(
+ "-m",
+ "--machine",
+ type="string",
+ dest="machine",
+ default="atmega644",
+ help="type of AVR machine to simulate",
+ )
+ opts.add_option(
+ "-s",
+ "--speed",
+ type="int",
+ dest="speed",
+ default=16000000,
+ help="machine speed",
+ )
+ opts.add_option(
+ "-r",
+ "--rate",
+ type="float",
+ dest="pacing_rate",
+ default=0.0,
+ help="real-time pacing rate",
+ )
+ opts.add_option(
+ "-b",
+ "--baud",
+ type="int",
+ dest="baud",
+ default=250000,
+ help="baud rate of the emulated serial port",
+ )
+ opts.add_option(
+ "-t",
+ "--trace",
+ type="string",
+ dest="trace",
+ help="signals to trace (? for help)",
+ )
+ opts.add_option(
+ "-p",
+ "--port",
+ type="string",
+ dest="port",
+ default="/tmp/pseudoserial",
+ help="pseudo-tty device to create for serial port",
+ )
deffile = os.path.splitext(os.path.basename(sys.argv[0]))[0] + ".vcd"
- opts.add_option("-f", "--tracefile", type="string", dest="tracefile",
- default=deffile, help="filename to write signal trace to")
+ opts.add_option(
+ "-f",
+ "--tracefile",
+ type="string",
+ dest="tracefile",
+ default=deffile,
+ help="filename to write signal trace to",
+ )
options, args = opts.parse_args()
if len(args) != 1:
opts.error("Incorrect number of arguments")
@@ -236,10 +300,11 @@ def main():
try:
io.run(fd)
trace.start()
- sc.RunTimeRange(0x7fff0000ffff0000)
+ sc.RunTimeRange(0x7FFF0000FFFF0000)
trace.finish()
finally:
os.unlink(ptyname)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py
index b3587384..fa4303ed 100644
--- a/scripts/buildcommands.py
+++ b/scripts/buildcommands.py
@@ -6,7 +6,8 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, subprocess, optparse, logging, shlex, socket, time, traceback
import json, zlib
-sys.path.append('./klippy')
+
+sys.path.append("./klippy")
import msgproto
FILEHEADER = """
@@ -19,10 +20,12 @@ FILEHEADER = """
#include "initial_pins.h"
"""
+
def error(msg):
sys.stderr.write(msg + "\n")
sys.exit(-1)
+
Handlers = []
@@ -30,23 +33,26 @@ Handlers = []
# C call list generation
######################################################################
+
# Create dynamic C functions that call a list of other C functions
class HandleCallList:
def __init__(self):
- self.call_lists = {'ctr_run_initfuncs': []}
- self.ctr_dispatch = { '_DECL_CALLLIST': self.decl_calllist }
+ self.call_lists = {"ctr_run_initfuncs": []}
+ self.ctr_dispatch = {"_DECL_CALLLIST": self.decl_calllist}
+
def decl_calllist(self, req):
funcname, callname = req.split()[1:]
self.call_lists.setdefault(funcname, []).append(callname)
+
def update_data_dictionary(self, data):
pass
+
def generate_code(self, options):
code = []
for funcname, funcs in self.call_lists.items():
- func_code = [' extern void %s(void);\n %s();' % (f, f)
- for f in funcs]
- if funcname == 'ctr_run_taskfuncs':
- add_poll = ' irq_poll();\n'
+ func_code = [" extern void %s(void);\n %s();" % (f, f) for f in funcs]
+ if funcname == "ctr_run_taskfuncs":
+ add_poll = " irq_poll();\n"
func_code = [add_poll + fc for fc in func_code]
func_code.append(add_poll)
fmt = """
@@ -59,6 +65,7 @@ void
code.append(fmt % (funcname, "\n".join(func_code).strip()))
return "".join(code)
+
Handlers.append(HandleCallList())
@@ -68,41 +75,49 @@ Handlers.append(HandleCallList())
STATIC_STRING_MIN = 2
+
# Generate a dynamic string to integer mapping
class HandleEnumerations:
def __init__(self):
self.static_strings = []
self.enumerations = {}
self.ctr_dispatch = {
- '_DECL_STATIC_STR': self.decl_static_str,
- 'DECL_ENUMERATION': self.decl_enumeration,
- 'DECL_ENUMERATION_RANGE': self.decl_enumeration_range
+ "_DECL_STATIC_STR": self.decl_static_str,
+ "DECL_ENUMERATION": self.decl_enumeration,
+ "DECL_ENUMERATION_RANGE": self.decl_enumeration_range,
}
+
def add_enumeration(self, enum, name, value):
enums = self.enumerations.setdefault(enum, {})
if name in enums and enums[name] != value:
- error("Conflicting definition for enumeration '%s %s'" % (
- enum, name))
+ error("Conflicting definition for enumeration '%s %s'" % (enum, name))
enums[name] = value
+
def decl_enumeration(self, req):
enum, name, value = req.split()[1:]
self.add_enumeration(enum, name, int(value, 0))
+
def decl_enumeration_range(self, req):
enum, name, value, count = req.split()[1:]
self.add_enumeration(enum, name, (int(value, 0), int(count, 0)))
+
def decl_static_str(self, req):
msg = req.split(None, 1)[1]
if msg not in self.static_strings:
self.static_strings.append(msg)
+
def update_data_dictionary(self, data):
for i, s in enumerate(self.static_strings):
self.add_enumeration("static_string_id", s, i + STATIC_STRING_MIN)
- data['enumerations'] = self.enumerations
+ data["enumerations"] = self.enumerations
+
def generate_code(self, options):
code = []
for i, s in enumerate(self.static_strings):
- code.append(' if (__builtin_strcmp(str, "%s") == 0)\n'
- ' return %d;\n' % (s, i + STATIC_STRING_MIN))
+ code.append(
+ ' if (__builtin_strcmp(str, "%s") == 0)\n'
+ " return %d;\n" % (s, i + STATIC_STRING_MIN)
+ )
fmt = """
uint8_t __always_inline
ctr_lookup_static_string(const char *str)
@@ -113,6 +128,7 @@ ctr_lookup_static_string(const char *str)
"""
return fmt % ("".join(code).strip(),)
+
HandlerEnumerations = HandleEnumerations()
Handlers.append(HandlerEnumerations)
@@ -121,32 +137,39 @@ Handlers.append(HandlerEnumerations)
# Constants
######################################################################
+
# Allow adding build time constants to the data dictionary
class HandleConstants:
def __init__(self):
self.constants = {}
self.ctr_dispatch = {
- 'DECL_CONSTANT': self.decl_constant,
- 'DECL_CONSTANT_STR': self.decl_constant_str,
+ "DECL_CONSTANT": self.decl_constant,
+ "DECL_CONSTANT_STR": self.decl_constant_str,
}
+
def set_value(self, name, value):
if name in self.constants and self.constants[name] != value:
error("Conflicting definition for constant '%s'" % name)
self.constants[name] = value
+
def decl_constant(self, req):
name, value = req.split()[1:]
self.set_value(name, int(value, 0))
+
def decl_constant_str(self, req):
name, value = req.split(None, 2)[1:]
value = value.strip()
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
self.set_value(name, value)
+
def update_data_dictionary(self, data):
- data['config'] = self.constants
+ data["config"] = self.constants
+
def generate_code(self, options):
return ""
+
HandlerConstants = HandleConstants()
Handlers.append(HandlerConstants)
@@ -155,37 +178,42 @@ Handlers.append(HandlerConstants)
# Initial pins
######################################################################
+
class HandleInitialPins:
def __init__(self):
self.initial_pins = []
- self.ctr_dispatch = { 'DECL_INITIAL_PINS': self.decl_initial_pins }
+ self.ctr_dispatch = {"DECL_INITIAL_PINS": self.decl_initial_pins}
+
def decl_initial_pins(self, req):
pins = req.split(None, 1)[1].strip()
if pins.startswith('"') and pins.endswith('"'):
pins = pins[1:-1]
if pins:
- self.initial_pins = [p.strip() for p in pins.split(',')]
+ self.initial_pins = [p.strip() for p in pins.split(",")]
HandlerConstants.decl_constant_str(
- "_DECL_CONSTANT_STR INITIAL_PINS "
- + ','.join(self.initial_pins))
+ "_DECL_CONSTANT_STR INITIAL_PINS " + ",".join(self.initial_pins)
+ )
+
def update_data_dictionary(self, data):
pass
+
def map_pins(self):
if not self.initial_pins:
return []
mp = msgproto.MessageParser()
mp.fill_enumerations(HandlerEnumerations.enumerations)
- pinmap = mp.get_enumerations().get('pin', {})
+ pinmap = mp.get_enumerations().get("pin", {})
out = []
for p in self.initial_pins:
flag = "IP_OUT_HIGH"
- if p.startswith('!'):
+ if p.startswith("!"):
flag = "0"
p = p[1:].strip()
if p not in pinmap:
error("Unknown initial pin '%s'" % (p,))
out.append("\n {%d, %s}, // %s" % (pinmap[p], flag, p))
return out
+
def generate_code(self, options):
out = self.map_pins()
fmt = """
@@ -193,7 +221,8 @@ const struct initial_pin_s initial_pins[] PROGMEM = {%s
};
const int initial_pins_size PROGMEM = ARRAY_SIZE(initial_pins);
"""
- return fmt % (''.join(out),)
+ return fmt % ("".join(out),)
+
Handlers.append(HandleInitialPins())
@@ -202,20 +231,26 @@ Handlers.append(HandleInitialPins())
# ARM IRQ vector table generation
######################################################################
+
# Create ARM IRQ vector table from interrupt handler declarations
class Handle_arm_irq:
def __init__(self):
self.irqs = {}
- self.ctr_dispatch = { 'DECL_ARMCM_IRQ': self.decl_armcm_irq }
+ self.ctr_dispatch = {"DECL_ARMCM_IRQ": self.decl_armcm_irq}
+
def decl_armcm_irq(self, req):
func, num = req.split()[1:]
num = int(num, 0)
if num in self.irqs and self.irqs[num] != func:
- error("Conflicting IRQ definition %d (old %s new %s)"
- % (num, self.irqs[num], func))
+ error(
+ "Conflicting IRQ definition %d (old %s new %s)"
+ % (num, self.irqs[num], func)
+ )
self.irqs[num] = func
+
def update_data_dictionary(self, data):
pass
+
def generate_code(self, options):
armcm_offset = 16
if 1 - armcm_offset not in self.irqs:
@@ -237,7 +272,8 @@ extern uint32_t _stack_end;
const void *VectorTable[] __visible __section(".vector_table") = {
%s};
"""
- return fmt % (''.join(defs), ''.join(table))
+ return fmt % ("".join(defs), "".join(table))
+
Handlers.append(Handle_arm_irq())
@@ -246,6 +282,7 @@ Handlers.append(Handle_arm_irq())
# Wire protocol commands and responses
######################################################################
+
# Dynamic command and response registration
class HandleCommandGeneration:
def __init__(self):
@@ -253,13 +290,14 @@ class HandleCommandGeneration:
self.encoders = []
self.msg_to_encid = dict(msgproto.DefaultMessages)
self.encid_to_msgid = {}
- self.messages_by_name = { m.split()[0]: m for m in self.msg_to_encid }
+ self.messages_by_name = {m.split()[0]: m for m in self.msg_to_encid}
self.all_param_types = {}
self.ctr_dispatch = {
- 'DECL_COMMAND_FLAGS': self.decl_command,
- '_DECL_ENCODER': self.decl_encoder,
- '_DECL_OUTPUT': self.decl_output
+ "DECL_COMMAND_FLAGS": self.decl_command,
+ "_DECL_ENCODER": self.decl_encoder,
+ "_DECL_OUTPUT": self.decl_output,
}
+
def decl_command(self, req):
funcname, flags, msgname = req.split()[1:4]
if msgname in self.commands:
@@ -270,6 +308,7 @@ class HandleCommandGeneration:
if m is not None and m != msg:
error("Conflicting definition for command '%s'" % msgname)
self.messages_by_name[msgname] = msg
+
def decl_encoder(self, req):
msg = req.split(None, 1)[1]
msgname = msg.split()[0]
@@ -278,15 +317,18 @@ class HandleCommandGeneration:
error("Conflicting definition for message '%s'" % msgname)
self.messages_by_name[msgname] = msg
self.encoders.append((msgname, msg))
+
def decl_output(self, req):
msg = req.split(None, 1)[1]
self.encoders.append((None, msg))
+
def convert_encoded_msgid(self, encoded_msgid):
if encoded_msgid >= 0x80:
- data = [(encoded_msgid >> 7) | 0x80, encoded_msgid & 0x7f]
+ data = [(encoded_msgid >> 7) | 0x80, encoded_msgid & 0x7F]
else:
data = [encoded_msgid]
return msgproto.PT_int32().parse(data, 0)[0]
+
def create_message_ids(self):
# Create unique ids for each message type
encoded_msgid = max(self.msg_to_encid.values())
@@ -296,31 +338,44 @@ class HandleCommandGeneration:
if msg not in self.msg_to_encid:
encoded_msgid += 1
self.msg_to_encid[msg] = encoded_msgid
- if encoded_msgid >= 1<<14:
+ if encoded_msgid >= 1 << 14:
# The mcu currently assumes all message ids encode to 1 or 2 bytes
error("Too many message ids")
self.encid_to_msgid = {
encoded_msgid: self.convert_encoded_msgid(encoded_msgid)
for encoded_msgid in self.msg_to_encid.values()
}
+
def update_data_dictionary(self, data):
# Convert ids to standard form (use both positive and negative numbers)
- msg_to_msgid = {msg: self.encid_to_msgid[encoded_msgid]
- for msg, encoded_msgid in self.msg_to_encid.items()}
- command_ids = [msg_to_msgid[msg]
- for msgname, msg in self.messages_by_name.items()
- if msgname in self.commands]
- response_ids = [msg_to_msgid[msg]
- for msgname, msg in self.messages_by_name.items()
- if msgname not in self.commands]
- data['commands'] = { msg: msgid for msg, msgid in msg_to_msgid.items()
- if msgid in command_ids }
- data['responses'] = { msg: msgid for msg, msgid in msg_to_msgid.items()
- if msgid in response_ids }
- output = {msg: msgid for msg, msgid in msg_to_msgid.items()
- if msgid not in command_ids and msgid not in response_ids}
+ msg_to_msgid = {
+ msg: self.encid_to_msgid[encoded_msgid]
+ for msg, encoded_msgid in self.msg_to_encid.items()
+ }
+ command_ids = [
+ msg_to_msgid[msg]
+ for msgname, msg in self.messages_by_name.items()
+ if msgname in self.commands
+ ]
+ response_ids = [
+ msg_to_msgid[msg]
+ for msgname, msg in self.messages_by_name.items()
+ if msgname not in self.commands
+ ]
+ data["commands"] = {
+ msg: msgid for msg, msgid in msg_to_msgid.items() if msgid in command_ids
+ }
+ data["responses"] = {
+ msg: msgid for msg, msgid in msg_to_msgid.items() if msgid in response_ids
+ }
+ output = {
+ msg: msgid
+ for msg, msgid in msg_to_msgid.items()
+ if msgid not in command_ids and msgid not in response_ids
+ }
if output:
- data['output'] = output
+ data["output"] = output
+
def build_parser(self, encoded_msgid, msgformat, msgtype):
if msgtype == "output":
param_types = msgproto.lookup_output_params(msgformat)
@@ -328,34 +383,46 @@ class HandleCommandGeneration:
else:
param_types = [t for name, t in msgproto.lookup_params(msgformat)]
comment = msgformat
- params = '0'
+ params = "0"
types = tuple([t.__class__.__name__ for t in param_types])
if types:
paramid = self.all_param_types.get(types)
if paramid is None:
paramid = len(self.all_param_types)
self.all_param_types[types] = paramid
- params = 'command_parameters%d' % (paramid,)
+ params = "command_parameters%d" % (paramid,)
out = """
// %s
.encoded_msgid=%d, // msgid=%d
.num_params=%d,
.param_types = %s,
-""" % (comment, encoded_msgid, self.encid_to_msgid[encoded_msgid],
- len(types), params)
- if msgtype == 'response':
- num_args = (len(types) + types.count('PT_progmem_buffer')
- + types.count('PT_buffer'))
+""" % (
+ comment,
+ encoded_msgid,
+ self.encid_to_msgid[encoded_msgid],
+ len(types),
+ params,
+ )
+ if msgtype == "response":
+ num_args = (
+ len(types) + types.count("PT_progmem_buffer") + types.count("PT_buffer")
+ )
out += " .num_args=%d," % (num_args,)
else:
msgid_size = 1
if encoded_msgid >= 0x80:
msgid_size = 2
- max_size = min(msgproto.MESSAGE_MAX,
- (msgproto.MESSAGE_MIN + msgid_size
- + sum([t.max_length for t in param_types])))
+ max_size = min(
+ msgproto.MESSAGE_MAX,
+ (
+ msgproto.MESSAGE_MIN
+ + msgid_size
+ + sum([t.max_length for t in param_types])
+ ),
+ )
out += " .max_size=%d," % (max_size,)
return out
+
def generate_responses_code(self):
encoder_defs = []
output_code = []
@@ -366,19 +433,20 @@ class HandleCommandGeneration:
if encoded_msgid in did_output:
continue
did_output[encoded_msgid] = True
- code = (' if (__builtin_strcmp(str, "%s") == 0)\n'
- ' return &command_encoder_%s;\n'
- % (msg, encoded_msgid))
+ code = (
+ ' if (__builtin_strcmp(str, "%s") == 0)\n'
+ " return &command_encoder_%s;\n" % (msg, encoded_msgid)
+ )
if msgname is None:
- parsercode = self.build_parser(encoded_msgid, msg, 'output')
+ parsercode = self.build_parser(encoded_msgid, msg, "output")
output_code.append(code)
else:
- parsercode = self.build_parser(encoded_msgid, msg, 'command')
+ parsercode = self.build_parser(encoded_msgid, msg, "command")
encoder_code.append(code)
encoder_defs.append(
"const struct command_encoder command_encoder_%s PROGMEM = {"
- " %s\n};\n" % (
- encoded_msgid, parsercode))
+ " %s\n};\n" % (encoded_msgid, parsercode)
+ )
fmt = """
%s
@@ -396,9 +464,12 @@ ctr_lookup_output(const char *str)
return NULL;
}
"""
- return fmt % ("".join(encoder_defs).strip(),
- "".join(encoder_code).strip(),
- "".join(output_code).strip())
+ return fmt % (
+ "".join(encoder_defs).strip(),
+ "".join(encoder_code).strip(),
+ "".join(output_code).strip(),
+ )
+
def generate_commands_code(self):
cmd_by_encid = {
self.msg_to_encid[self.messages_by_name.get(msgname, msgname)]: cmd
@@ -407,19 +478,21 @@ ctr_lookup_output(const char *str)
max_cmd_encid = max(cmd_by_encid.keys())
index = []
externs = {}
- for encoded_msgid in range(max_cmd_encid+1):
+ for encoded_msgid in range(max_cmd_encid + 1):
if encoded_msgid not in cmd_by_encid:
index.append(" {\n},")
continue
funcname, flags, msgname = cmd_by_encid[encoded_msgid]
msg = self.messages_by_name[msgname]
externs[funcname] = 1
- parsercode = self.build_parser(encoded_msgid, msg, 'response')
- index.append(" {%s\n .flags=%s,\n .func=%s\n}," % (
- parsercode, flags, funcname))
+ parsercode = self.build_parser(encoded_msgid, msg, "response")
+ index.append(
+ " {%s\n .flags=%s,\n .func=%s\n}," % (parsercode, flags, funcname)
+ )
index = "".join(index).strip()
- externs = "\n".join(["extern void "+funcname+"(uint32_t*);"
- for funcname in sorted(externs)])
+ externs = "\n".join(
+ ["extern void " + funcname + "(uint32_t*);" for funcname in sorted(externs)]
+ )
fmt = """
%s
@@ -430,17 +503,22 @@ const struct command_parser command_index[] PROGMEM = {
const uint16_t command_index_size PROGMEM = ARRAY_SIZE(command_index);
"""
return fmt % (externs, index)
+
def generate_param_code(self):
- sorted_param_types = sorted(
- [(i, a) for a, i in self.all_param_types.items()])
- params = ['']
+ sorted_param_types = sorted([(i, a) for a, i in self.all_param_types.items()])
+ params = [""]
for paramid, argtypes in sorted_param_types:
params.append(
- 'static const uint8_t command_parameters%d[] PROGMEM = {\n'
- ' %s };' % (
- paramid, ', '.join(argtypes),))
- params.append('')
+ "static const uint8_t command_parameters%d[] PROGMEM = {\n"
+ " %s };"
+ % (
+ paramid,
+ ", ".join(argtypes),
+ )
+ )
+ params.append("")
return "\n".join(params)
+
def generate_code(self, options):
self.create_message_ids()
parsercode = self.generate_responses_code()
@@ -448,6 +526,7 @@ const uint16_t command_index_size PROGMEM = ARRAY_SIZE(command_index);
paramcode = self.generate_param_code()
return paramcode + parsercode + cmdcode
+
Handlers.append(HandleCommandGeneration())
@@ -455,6 +534,7 @@ Handlers.append(HandleCommandGeneration())
# Version generation
######################################################################
+
# Run program and return the specified output
def check_output(prog):
logging.debug("Running %s" % (repr(prog),))
@@ -469,26 +549,28 @@ def check_output(prog):
if retcode:
return ""
try:
- return str(output.decode('utf8'))
+ return str(output.decode("utf8"))
except UnicodeError:
logging.debug("Exception on decode: %s" % (traceback.format_exc(),))
return ""
+
# Obtain version info from "git" program
def git_version():
- if not os.path.exists('.git'):
+ if not os.path.exists(".git"):
logging.debug("No '.git' file/directory found")
return ""
ver = check_output("git describe --always --tags --long --dirty").strip()
logging.debug("Got git version: %s" % (repr(ver),))
return ver
+
def build_version(extra, cleanbuild):
version = git_version()
if not version:
cleanbuild = False
version = "?"
- elif 'dirty' in version:
+ elif "dirty" in version:
cleanbuild = False
if not cleanbuild:
btime = time.strftime("%Y%m%d_%H%M%S")
@@ -496,29 +578,31 @@ def build_version(extra, cleanbuild):
version = "%s-%s-%s" % (version, btime, hostname)
return version + extra
+
# Run "tool --version" for each specified tool and extract versions
def tool_versions(tools):
- tools = [t.strip() for t in tools.split(';')]
- versions = ['', '']
+ tools = [t.strip() for t in tools.split(";")]
+ versions = ["", ""]
success = 0
for tool in tools:
# Extract first line from "tool --version" output
- verstr = check_output("%s --version" % (tool,)).split('\n')[0]
+ verstr = check_output("%s --version" % (tool,)).split("\n")[0]
# Check if this tool looks like a binutils program
isbinutils = 0
- if verstr.startswith('GNU '):
+ if verstr.startswith("GNU "):
isbinutils = 1
verstr = verstr[4:]
# Extract version information and exclude program name
- if ' ' not in verstr:
+ if " " not in verstr:
continue
- prog, ver = verstr.split(' ', 1)
+ prog, ver = verstr.split(" ", 1)
if not prog or not ver:
continue
# Check for any version conflicts
if versions[isbinutils] and versions[isbinutils] != ver:
- logging.debug("Mixed version %s vs %s" % (
- repr(versions[isbinutils]), repr(ver)))
+ logging.debug(
+ "Mixed version %s vs %s" % (repr(versions[isbinutils]), repr(ver))
+ )
versions[isbinutils] = "mixed"
continue
versions[isbinutils] = ver
@@ -526,22 +610,28 @@ def tool_versions(tools):
cleanbuild = versions[0] and versions[1] and success == len(tools)
return cleanbuild, "gcc: %s binutils: %s" % (versions[0], versions[1])
+
# Add version information to the data dictionary
class HandleVersions:
def __init__(self):
self.ctr_dispatch = {}
self.toolstr = self.version = ""
+
def update_data_dictionary(self, data):
- data['version'] = self.version
- data['build_versions'] = self.toolstr
- data['app'] = 'Klipper'
- data['license'] = 'GNU GPLv3'
+ data["version"] = self.version
+ data["build_versions"] = self.toolstr
+ data["app"] = "Klipper"
+ data["license"] = "GNU GPLv3"
+
def generate_code(self, options):
cleanbuild, self.toolstr = tool_versions(options.tools)
self.version = build_version(options.extra, cleanbuild)
sys.stdout.write("Version: %s\n" % (self.version,))
return "\n// version: %s\n// build_versions: %s\n" % (
- self.version, self.toolstr)
+ self.version,
+ self.toolstr,
+ )
+
Handlers.append(HandleVersions())
@@ -550,22 +640,25 @@ Handlers.append(HandleVersions())
# Identify data dictionary generation
######################################################################
+
# Automatically generate the wire protocol data dictionary
class HandleIdentify:
def __init__(self):
self.ctr_dispatch = {}
+
def update_data_dictionary(self, data):
pass
+
def generate_code(self, options):
# Generate data dictionary
data = {}
for h in Handlers:
h.update_data_dictionary(data)
- datadict = json.dumps(data, separators=(',', ':'), sort_keys=True)
+ datadict = json.dumps(data, separators=(",", ":"), sort_keys=True)
# Write data dictionary
if options.write_dictionary:
- f = open(options.write_dictionary, 'w')
+ f = open(options.write_dictionary, "w")
f.write(datadict)
f.close()
@@ -574,7 +667,7 @@ class HandleIdentify:
out = []
for i in range(len(zdatadict)):
if i % 8 == 0:
- out.append('\n ')
+ out.append("\n ")
out.append(" 0x%02x," % (zdatadict[i],))
fmt = """
const uint8_t command_identify_data[] PROGMEM = {%s
@@ -584,7 +677,8 @@ const uint8_t command_identify_data[] PROGMEM = {%s
const uint32_t command_identify_size PROGMEM
= ARRAY_SIZE(command_identify_data);
"""
- return fmt % (''.join(out), len(zdatadict), len(datadict))
+ return fmt % ("".join(out), len(zdatadict), len(datadict))
+
Handlers.append(HandleIdentify())
@@ -593,17 +687,30 @@ Handlers.append(HandleIdentify())
# Main code
######################################################################
+
def main():
usage = "%prog [options] <cmd section file> <output.c>"
opts = optparse.OptionParser(usage)
- opts.add_option("-e", "--extra", dest="extra", default="",
- help="extra version string to append to version")
- opts.add_option("-d", dest="write_dictionary",
- help="file to write mcu protocol dictionary")
- opts.add_option("-t", "--tools", dest="tools", default="",
- help="list of build programs to extract version from")
- opts.add_option("-v", action="store_true", dest="verbose",
- help="enable debug messages")
+ opts.add_option(
+ "-e",
+ "--extra",
+ dest="extra",
+ default="",
+ help="extra version string to append to version",
+ )
+ opts.add_option(
+ "-d", dest="write_dictionary", help="file to write mcu protocol dictionary"
+ )
+ opts.add_option(
+ "-t",
+ "--tools",
+ dest="tools",
+ default="",
+ help="list of build programs to extract version from",
+ )
+ opts.add_option(
+ "-v", action="store_true", dest="verbose", help="enable debug messages"
+ )
options, args = opts.parse_args()
if len(args) != 2:
@@ -613,11 +720,11 @@ def main():
logging.basicConfig(level=logging.DEBUG)
# Parse request file
- ctr_dispatch = { k: v for h in Handlers for k, v in h.ctr_dispatch.items() }
- f = open(incmdfile, 'r')
+ ctr_dispatch = {k: v for h in Handlers for k, v in h.ctr_dispatch.items()}
+ f = open(incmdfile, "r")
data = f.read()
f.close()
- for req in data.split('\n'):
+ for req in data.split("\n"):
req = req.lstrip()
if not req:
continue
@@ -628,9 +735,10 @@ def main():
# Write output
code = "".join([FILEHEADER] + [h.generate_code(options) for h in Handlers])
- f = open(outcfile, 'w')
+ f = open(outcfile, "w")
f.write(code)
f.close()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/calibrate_shaper.py b/scripts/calibrate_shaper.py
index b56ce5da..6109d843 100755
--- a/scripts/calibrate_shaper.py
+++ b/scripts/calibrate_shaper.py
@@ -9,40 +9,58 @@ from __future__ import print_function
import importlib, optparse, os, sys
from textwrap import wrap
import numpy as np, matplotlib
-sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)),
- '..', 'klippy'))
-shaper_calibrate = importlib.import_module('.shaper_calibrate', 'extras')
-MAX_TITLE_LENGTH=65
+sys.path.append(
+ os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "klippy")
+)
+shaper_calibrate = importlib.import_module(".shaper_calibrate", "extras")
+
+MAX_TITLE_LENGTH = 65
+
def parse_log(logname):
with open(logname) as f:
for header in f:
- if not header.startswith('#'):
+ if not header.startswith("#"):
break
- if not header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'):
+ if not header.startswith("freq,psd_x,psd_y,psd_z,psd_xyz"):
# Raw accelerometer data
- return np.loadtxt(logname, comments='#', delimiter=',')
+ return np.loadtxt(logname, comments="#", delimiter=",")
# Parse power spectral density data
- data = np.loadtxt(logname, skiprows=1, comments='#', delimiter=',')
+ data = np.loadtxt(logname, skiprows=1, comments="#", delimiter=",")
calibration_data = shaper_calibrate.CalibrationData(
- freq_bins=data[:,0], psd_sum=data[:,4],
- psd_x=data[:,1], psd_y=data[:,2], psd_z=data[:,3])
+ freq_bins=data[:, 0],
+ psd_sum=data[:, 4],
+ psd_x=data[:, 1],
+ psd_y=data[:, 2],
+ psd_z=data[:, 3],
+ )
calibration_data.set_numpy(np)
# If input shapers are present in the CSV file, the frequency
# response is already normalized to input frequencies
- if 'mzv' not in header:
+ if "mzv" not in header:
calibration_data.normalize_to_frequencies()
return calibration_data
+
######################################################################
# Shaper calibration
######################################################################
+
# Find the best shaper parameters
-def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv,
- shaper_freqs, max_smoothing, test_damping_ratios,
- max_freq):
+def calibrate_shaper(
+ datas,
+ csv_output,
+ *,
+ shapers,
+ damping_ratio,
+ scv,
+ shaper_freqs,
+ max_smoothing,
+ test_damping_ratios,
+ max_freq
+):
helper = shaper_calibrate.ShaperCalibrate(printer=None)
if isinstance(datas[0], shaper_calibrate.CalibrationData):
calibration_data = datas[0]
@@ -55,28 +73,35 @@ def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv,
calibration_data.add_data(helper.process_accelerometer_data(data))
calibration_data.normalize_to_frequencies()
-
shaper, all_shapers = helper.find_best_shaper(
- calibration_data, shapers=shapers, damping_ratio=damping_ratio,
- scv=scv, shaper_freqs=shaper_freqs, max_smoothing=max_smoothing,
- test_damping_ratios=test_damping_ratios, max_freq=max_freq,
- logger=print)
+ calibration_data,
+ shapers=shapers,
+ damping_ratio=damping_ratio,
+ scv=scv,
+ shaper_freqs=shaper_freqs,
+ max_smoothing=max_smoothing,
+ test_damping_ratios=test_damping_ratios,
+ max_freq=max_freq,
+ logger=print,
+ )
if not shaper:
- print("No recommended shaper, possibly invalid value for --shapers=%s" %
- (','.join(shapers)))
+ print(
+ "No recommended shaper, possibly invalid value for --shapers=%s"
+ % (",".join(shapers))
+ )
return None, None, None
print("Recommended shaper is %s @ %.1f Hz" % (shaper.name, shaper.freq))
if csv_output is not None:
- helper.save_calibration_data(
- csv_output, calibration_data, all_shapers)
+ helper.save_calibration_data(csv_output, calibration_data, all_shapers)
return shaper.name, all_shapers, calibration_data
+
######################################################################
# Plot frequency response and suggested input shapers
######################################################################
-def plot_freq_response(lognames, calibration_data, shapers,
- selected_shaper, max_freq):
+
+def plot_freq_response(lognames, calibration_data, shapers, selected_shaper, max_freq):
freqs = calibration_data.freq_bins
psd = calibration_data.psd_sum[freqs <= max_freq]
px = calibration_data.psd_x[freqs <= max_freq]
@@ -85,89 +110,140 @@ def plot_freq_response(lognames, calibration_data, shapers,
freqs = freqs[freqs <= max_freq]
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
+ fontP.set_size("x-small")
fig, ax = matplotlib.pyplot.subplots()
- ax.set_xlabel('Frequency, Hz')
+ ax.set_xlabel("Frequency, Hz")
ax.set_xlim([0, max_freq])
- ax.set_ylabel('Power spectral density')
+ ax.set_ylabel("Power spectral density")
- ax.plot(freqs, psd, label='X+Y+Z', color='purple')
- ax.plot(freqs, px, label='X', color='red')
- ax.plot(freqs, py, label='Y', color='green')
- ax.plot(freqs, pz, label='Z', color='blue')
+ ax.plot(freqs, psd, label="X+Y+Z", color="purple")
+ ax.plot(freqs, px, label="X", color="red")
+ ax.plot(freqs, py, label="Y", color="green")
+ ax.plot(freqs, pz, label="Z", color="blue")
- title = "Frequency response and shapers (%s)" % (', '.join(lognames))
+ title = "Frequency response and shapers (%s)" % (", ".join(lognames))
ax.set_title("\n".join(wrap(title, MAX_TITLE_LENGTH)))
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(5))
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
- ax.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
- ax.grid(which='major', color='grey')
- ax.grid(which='minor', color='lightgrey')
+ ax.ticklabel_format(axis="y", style="scientific", scilimits=(0, 0))
+ ax.grid(which="major", color="grey")
+ ax.grid(which="minor", color="lightgrey")
ax2 = ax.twinx()
- ax2.set_ylabel('Shaper vibration reduction (ratio)')
+ ax2.set_ylabel("Shaper vibration reduction (ratio)")
best_shaper_vals = None
for shaper in shapers:
label = "%s (%.1f Hz, vibr=%.1f%%, sm~=%.2f, accel<=%.f)" % (
- shaper.name.upper(), shaper.freq,
- shaper.vibrs * 100., shaper.smoothing,
- round(shaper.max_accel / 100.) * 100.)
- linestyle = 'dotted'
+ shaper.name.upper(),
+ shaper.freq,
+ shaper.vibrs * 100.0,
+ shaper.smoothing,
+ round(shaper.max_accel / 100.0) * 100.0,
+ )
+ linestyle = "dotted"
if shaper.name == selected_shaper:
- linestyle = 'dashdot'
+ linestyle = "dashdot"
best_shaper_vals = shaper.vals
ax2.plot(freqs, shaper.vals, label=label, linestyle=linestyle)
- ax.plot(freqs, psd * best_shaper_vals,
- label='After\nshaper', color='cyan')
+ ax.plot(freqs, psd * best_shaper_vals, label="After\nshaper", color="cyan")
# A hack to add a human-readable shaper recommendation to legend
- ax2.plot([], [], ' ',
- label="Recommended shaper: %s" % (selected_shaper.upper()))
+ ax2.plot([], [], " ", label="Recommended shaper: %s" % (selected_shaper.upper()))
- ax.legend(loc='upper left', prop=fontP)
- ax2.legend(loc='upper right', prop=fontP)
+ ax.legend(loc="upper left", prop=fontP)
+ ax2.legend(loc="upper right", prop=fontP)
fig.tight_layout()
return fig
+
######################################################################
# Startup
######################################################################
+
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
- matplotlib.rcParams.update({'figure.autolayout': True})
- matplotlib.use('Agg')
+ matplotlib.rcParams.update({"figure.autolayout": True})
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def main():
# Parse command-line arguments
usage = "%prog [options] <logs>"
opts = optparse.OptionParser(usage)
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
- opts.add_option("-c", "--csv", type="string", dest="csv",
- default=None, help="filename of output csv file")
- opts.add_option("-f", "--max_freq", type="float", default=200.,
- help="maximum frequency to plot")
- opts.add_option("-s", "--max_smoothing", type="float", dest="max_smoothing",
- default=None, help="maximum shaper smoothing to allow")
- opts.add_option("--scv", "--square_corner_velocity", type="float",
- dest="scv", default=5., help="square corner velocity")
- opts.add_option("--shaper_freq", type="string", dest="shaper_freq",
- default=None, help="shaper frequency(-ies) to test, " +
- "either a comma-separated list of floats, or a range in " +
- "the format [start]:end[:step]")
- opts.add_option("--shapers", type="string", dest="shapers", default=None,
- help="a comma-separated list of shapers to test")
- opts.add_option("--damping_ratio", type="float", dest="damping_ratio",
- default=None, help="shaper damping_ratio parameter")
- opts.add_option("--test_damping_ratios", type="string",
- dest="test_damping_ratios", default=None,
- help="a comma-separated liat of damping ratios to test " +
- "input shaper for")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
+ opts.add_option(
+ "-c",
+ "--csv",
+ type="string",
+ dest="csv",
+ default=None,
+ help="filename of output csv file",
+ )
+ opts.add_option(
+ "-f",
+ "--max_freq",
+ type="float",
+ default=200.0,
+ help="maximum frequency to plot",
+ )
+ opts.add_option(
+ "-s",
+ "--max_smoothing",
+ type="float",
+ dest="max_smoothing",
+ default=None,
+ help="maximum shaper smoothing to allow",
+ )
+ opts.add_option(
+ "--scv",
+ "--square_corner_velocity",
+ type="float",
+ dest="scv",
+ default=5.0,
+ help="square corner velocity",
+ )
+ opts.add_option(
+ "--shaper_freq",
+ type="string",
+ dest="shaper_freq",
+ default=None,
+ help="shaper frequency(-ies) to test, "
+ + "either a comma-separated list of floats, or a range in "
+ + "the format [start]:end[:step]",
+ )
+ opts.add_option(
+ "--shapers",
+ type="string",
+ dest="shapers",
+ default=None,
+ help="a comma-separated list of shapers to test",
+ )
+ opts.add_option(
+ "--damping_ratio",
+ type="float",
+ dest="damping_ratio",
+ default=None,
+ help="shaper damping_ratio parameter",
+ )
+ opts.add_option(
+ "--test_damping_ratios",
+ type="string",
+ dest="test_damping_ratios",
+ default=None,
+ help="a comma-separated liat of damping ratios to test " + "input shaper for",
+ )
options, args = opts.parse_args()
if len(args) < 1:
opts.error("Incorrect number of arguments")
@@ -177,59 +253,68 @@ def main():
max_freq = options.max_freq
if options.shaper_freq is None:
shaper_freqs = []
- elif options.shaper_freq.find(':') >= 0:
+ elif options.shaper_freq.find(":") >= 0:
freq_start = None
freq_end = None
freq_step = None
try:
- freqs_parsed = options.shaper_freq.partition(':')
+ freqs_parsed = options.shaper_freq.partition(":")
if freqs_parsed[0]:
freq_start = float(freqs_parsed[0])
- freqs_parsed = freqs_parsed[-1].partition(':')
+ freqs_parsed = freqs_parsed[-1].partition(":")
freq_end = float(freqs_parsed[0])
if freq_start and freq_start > freq_end:
- opts.error("Invalid --shaper_freq param: start range larger " +
- "than its end")
- if freqs_parsed[-1].find(':') >= 0:
+ opts.error(
+ "Invalid --shaper_freq param: start range larger " + "than its end"
+ )
+ if freqs_parsed[-1].find(":") >= 0:
opts.error("Invalid --shaper_freq param format")
if freqs_parsed[-1]:
freq_step = float(freqs_parsed[-1])
except ValueError:
- opts.error("--shaper_freq param does not specify correct range " +
- "in the format [start]:end[:step]")
+ opts.error(
+ "--shaper_freq param does not specify correct range "
+ + "in the format [start]:end[:step]"
+ )
shaper_freqs = (freq_start, freq_end, freq_step)
- max_freq = max(max_freq, freq_end * 4./3.)
+ max_freq = max(max_freq, freq_end * 4.0 / 3.0)
else:
try:
- shaper_freqs = [float(s) for s in options.shaper_freq.split(',')]
+ shaper_freqs = [float(s) for s in options.shaper_freq.split(",")]
except ValueError:
opts.error("invalid floating point value in --shaper_freq param")
- max_freq = max(max_freq, max(shaper_freqs) * 4./3.)
+ max_freq = max(max_freq, max(shaper_freqs) * 4.0 / 3.0)
if options.test_damping_ratios:
try:
- test_damping_ratios = [float(s) for s in
- options.test_damping_ratios.split(',')]
+ test_damping_ratios = [
+ float(s) for s in options.test_damping_ratios.split(",")
+ ]
except ValueError:
- opts.error("invalid floating point value in " +
- "--test_damping_ratios param")
+ opts.error(
+ "invalid floating point value in " + "--test_damping_ratios param"
+ )
else:
test_damping_ratios = None
if options.shapers is None:
shapers = None
else:
- shapers = options.shapers.lower().split(',')
+ shapers = options.shapers.lower().split(",")
# Parse data
datas = [parse_log(fn) for fn in args]
# Calibrate shaper and generate outputs
selected_shaper, shapers, calibration_data = calibrate_shaper(
- datas, options.csv, shapers=shapers,
- damping_ratio=options.damping_ratio,
- scv=options.scv, shaper_freqs=shaper_freqs,
- max_smoothing=options.max_smoothing,
- test_damping_ratios=test_damping_ratios,
- max_freq=max_freq)
+ datas,
+ options.csv,
+ shapers=shapers,
+ damping_ratio=options.damping_ratio,
+ scv=options.scv,
+ shaper_freqs=shaper_freqs,
+ max_smoothing=options.max_smoothing,
+ test_damping_ratios=test_damping_ratios,
+ max_freq=max_freq,
+ )
if selected_shaper is None:
return
@@ -237,8 +322,9 @@ def main():
# Draw graph
setup_matplotlib(options.output is not None)
- fig = plot_freq_response(args, calibration_data, shapers,
- selected_shaper, max_freq)
+ fig = plot_freq_response(
+ args, calibration_data, shapers, selected_shaper, max_freq
+ )
# Show graph
if options.output is None:
@@ -247,5 +333,6 @@ def main():
fig.set_size_inches(8, 6)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/canbus_query.py b/scripts/canbus_query.py
index 52dd4706..12032ee7 100644
--- a/scripts/canbus_query.py
+++ b/scripts/canbus_query.py
@@ -7,49 +7,66 @@
import sys, os, optparse, time
import can
-CANBUS_ID_ADMIN = 0x3f0
+CANBUS_ID_ADMIN = 0x3F0
CMD_QUERY_UNASSIGNED = 0x00
RESP_NEED_NODEID = 0x20
CMD_SET_KLIPPER_NODEID = 0x01
CMD_SET_CANBOOT_NODEID = 0x11
+
def query_unassigned(canbus_iface):
# Open CAN socket
- filters = [{"can_id": CANBUS_ID_ADMIN + 1, "can_mask": 0x7ff,
- "extended": False}]
- bus = can.interface.Bus(channel=canbus_iface, can_filters=filters,
- bustype='socketcan')
+ filters = [{"can_id": CANBUS_ID_ADMIN + 1, "can_mask": 0x7FF, "extended": False}]
+ bus = can.interface.Bus(
+ channel=canbus_iface, can_filters=filters, bustype="socketcan"
+ )
# Send query
- msg = can.Message(arbitration_id=CANBUS_ID_ADMIN,
- data=[CMD_QUERY_UNASSIGNED], is_extended_id=False)
+ msg = can.Message(
+ arbitration_id=CANBUS_ID_ADMIN,
+ data=[CMD_QUERY_UNASSIGNED],
+ is_extended_id=False,
+ )
bus.send(msg)
# Read responses
found_ids = {}
start_time = curtime = time.time()
while 1:
- tdiff = start_time + 2. - curtime
- if tdiff <= 0.:
+ tdiff = start_time + 2.0 - curtime
+ if tdiff <= 0.0:
break
msg = bus.recv(tdiff)
curtime = time.time()
- if (msg is None or msg.arbitration_id != CANBUS_ID_ADMIN + 1
- or msg.dlc < 7 or msg.data[0] != RESP_NEED_NODEID):
+ if (
+ msg is None
+ or msg.arbitration_id != CANBUS_ID_ADMIN + 1
+ or msg.dlc < 7
+ or msg.data[0] != RESP_NEED_NODEID
+ ):
continue
- uuid = sum([v << ((5-i)*8) for i, v in enumerate(msg.data[1:7])])
+ uuid = sum([v << ((5 - i) * 8) for i, v in enumerate(msg.data[1:7])])
if uuid in found_ids:
continue
found_ids[uuid] = 1
AppNames = {
CMD_SET_KLIPPER_NODEID: "Klipper",
- CMD_SET_CANBOOT_NODEID: "CanBoot"
+ CMD_SET_CANBOOT_NODEID: "CanBoot",
}
app_id = CMD_SET_KLIPPER_NODEID
if msg.dlc > 7:
app_id = msg.data[7]
app_name = AppNames.get(app_id, "Unknown")
- sys.stdout.write("Found canbus_uuid=%012x, Application: %s\n"
- % (uuid, app_name))
- sys.stdout.write("Total %d uuids found\n" % (len(found_ids,)))
+ sys.stdout.write(
+ "Found canbus_uuid=%012x, Application: %s\n" % (uuid, app_name)
+ )
+ sys.stdout.write(
+ "Total %d uuids found\n"
+ % (
+ len(
+ found_ids,
+ )
+ )
+ )
+
def main():
usage = "%prog [options] <can interface>"
@@ -60,5 +77,6 @@ def main():
canbus_iface = args[0]
query_unassigned(canbus_iface)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/check_whitespace.py b/scripts/check_whitespace.py
index fe8c7ae8..9223213a 100755
--- a/scripts/check_whitespace.py
+++ b/scripts/check_whitespace.py
@@ -8,6 +8,7 @@ import sys, os.path, unicodedata
HaveError = False
+
def report_error(filename, lineno, msg):
global HaveError
if not HaveError:
@@ -15,10 +16,11 @@ def report_error(filename, lineno, msg):
HaveError = True
sys.stderr.write("%s:%d: %s\n" % (filename, lineno + 1, msg))
+
def check_file(filename):
# Open and read file
try:
- f = open(filename, 'rb')
+ f = open(filename, "rb")
data = f.read()
f.close()
except IOError:
@@ -27,37 +29,37 @@ def check_file(filename):
# Empty files are okay
return
# Do checks
- is_source_code = any([filename.endswith(s) for s in ['.c', '.h', '.py']])
+ is_source_code = any([filename.endswith(s) for s in [".c", ".h", ".py"]])
lineno = 0
- for lineno, line in enumerate(data.split(b'\n')):
+ for lineno, line in enumerate(data.split(b"\n")):
# Verify line is valid utf-8
try:
- line = line.decode('utf-8')
+ line = line.decode("utf-8")
except UnicodeDecodeError:
report_error(filename, lineno, "Found non utf-8 character")
continue
# Check for control characters
for c in line:
- if unicodedata.category(c).startswith('C'):
+ if unicodedata.category(c).startswith("C"):
char_name = repr(c)
- if c == '\t':
- if os.path.basename(filename).lower() == 'makefile':
+ if c == "\t":
+ if os.path.basename(filename).lower() == "makefile":
continue
- char_name = 'tab'
- report_error(filename, lineno, "Invalid %s character" % (
- char_name,))
+ char_name = "tab"
+ report_error(filename, lineno, "Invalid %s character" % (char_name,))
break
# Check for trailing space
- if line.endswith(' ') or line.endswith('\t'):
+ if line.endswith(" ") or line.endswith("\t"):
report_error(filename, lineno, "Line has trailing spaces")
# Check for more than 80 characters
if is_source_code and len(line) > 80:
report_error(filename, lineno, "Line longer than 80 characters")
- if not data.endswith(b'\n'):
+ if not data.endswith(b"\n"):
report_error(filename, lineno, "No newline at end of file")
- if data.endswith(b'\n\n'):
+ if data.endswith(b"\n\n"):
report_error(filename, lineno, "Extra newlines at end of file")
+
def main():
files = sys.argv[1:]
for filename in files:
@@ -66,5 +68,6 @@ def main():
sys.stderr.write("\n\n")
sys.exit(-1)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/checkstack.py b/scripts/checkstack.py
index 1a6605fe..6ae9c473 100755
--- a/scripts/checkstack.py
+++ b/scripts/checkstack.py
@@ -25,6 +25,7 @@ OUTPUTDESC = """
# insn_addr:called_function [u+c,t,usage_to_yield_point]
"""
+
class function:
def __init__(self, funcaddr, funcname):
self.funcaddr = funcaddr
@@ -37,10 +38,12 @@ class function:
# called_funcs = [(insnaddr, calladdr, stackusage), ...]
self.called_funcs = []
self.subfuncs = {}
+
# Update function info with a found "yield" point.
def noteYield(self, stackusage):
if self.yield_usage < stackusage:
self.yield_usage = stackusage
+
# Update function info with a found "call" point.
def noteCall(self, insnaddr, calladdr, stackusage):
if (calladdr, stackusage) in self.subfuncs:
@@ -49,6 +52,7 @@ class function:
self.called_funcs.append((insnaddr, calladdr, stackusage))
self.subfuncs[(calladdr, stackusage)] = 1
+
# Find out maximum stack usage for a function
def calcmaxstack(info, funcs):
if info.max_stack_usage is not None:
@@ -66,7 +70,7 @@ def calcmaxstack(info, funcs):
if callinfo.funcname not in seenbefore:
seenbefore[callinfo.funcname] = 1
total_calls += callinfo.total_calls + 1
- funcnameroot = callinfo.funcname.split('.')[0]
+ funcnameroot = callinfo.funcname.split(".")[0]
if funcnameroot in IGNORE:
# This called function is ignored - don't contribute it to
# the max stack.
@@ -84,12 +88,15 @@ def calcmaxstack(info, funcs):
info.max_yield_usage = max_yield_usage
info.total_calls = total_calls
+
# Try to arrange output so that functions that call each other are
# near each other.
def orderfuncs(funcaddrs, availfuncs):
- l = [(availfuncs[funcaddr].total_calls
- , availfuncs[funcaddr].funcname, funcaddr)
- for funcaddr in funcaddrs if funcaddr in availfuncs]
+ l = [
+ (availfuncs[funcaddr].total_calls, availfuncs[funcaddr].funcname, funcaddr)
+ for funcaddr in funcaddrs
+ if funcaddr in availfuncs
+ ]
l.sort()
l.reverse()
out = []
@@ -103,17 +110,22 @@ def orderfuncs(funcaddrs, availfuncs):
out = out + orderfuncs(calladdrs, availfuncs) + [info]
return out
-hex_s = r'[0-9a-f]+'
-re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
+
+hex_s = r"[0-9a-f]+"
+re_func = re.compile(r"^(?P<funcaddr>" + hex_s + r") <(?P<func>.*)>:$")
re_asm = re.compile(
- r'^[ ]*(?P<insnaddr>' + hex_s
- + r'):\t[^\t]*\t(?P<insn>[^\t]+?)(?P<params>\t[^;]*)?'
- + r'[ ]*(; (?P<calladdr>0x' + hex_s
- + r') <(?P<ref>.*)>)?$')
+ r"^[ ]*(?P<insnaddr>"
+ + hex_s
+ + r"):\t[^\t]*\t(?P<insn>[^\t]+?)(?P<params>\t[^;]*)?"
+ + r"[ ]*(; (?P<calladdr>0x"
+ + hex_s
+ + r") <(?P<ref>.*)>)?$"
+)
+
def main():
unknownfunc = function(None, "<unknown>")
- indirectfunc = function(-1, '<indirect>')
+ indirectfunc = function(-1, "<indirect>")
unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0
unknownfunc.max_yield_usage = indirectfunc.max_yield_usage = -1
funcs = {-1: indirectfunc}
@@ -128,38 +140,38 @@ def main():
m = re_func.match(line)
if m is not None:
# Found function
- funcaddr = int(m.group('funcaddr'), 16)
- funcs[funcaddr] = cur = function(funcaddr, m.group('func'))
+ funcaddr = int(m.group("funcaddr"), 16)
+ funcs[funcaddr] = cur = function(funcaddr, m.group("func"))
stackusage = 0
atstart = 1
continue
m = re_asm.match(line)
if m is None:
datalines.setdefault(funcaddr, []).append(line)
- #print("other", repr(line))
+ # print("other", repr(line))
continue
- insn = m.group('insn')
+ insn = m.group("insn")
- if insn == 'push':
+ if insn == "push":
stackusage += 1
continue
- if insn == 'rcall' and m.group('params').strip() == '.+0':
+ if insn == "rcall" and m.group("params").strip() == ".+0":
stackusage += 2
continue
if atstart:
- if insn in ['in', 'eor']:
+ if insn in ["in", "eor"]:
continue
cur.basic_stack_usage = stackusage
atstart = 0
- insnaddr = m.group('insnaddr')
- calladdr = m.group('calladdr')
+ insnaddr = m.group("insnaddr")
+ calladdr = m.group("calladdr")
if calladdr is None:
- if insn == 'ijmp':
+ if insn == "ijmp":
# Indirect tail call
cur.noteCall(insnaddr, -1, 0)
- elif insn == 'icall':
+ elif insn == "icall":
cur.noteCall(insnaddr, -1, stackusage + 2)
else:
# misc instruction
@@ -167,17 +179,17 @@ def main():
else:
# Jump or call insn
calladdr = int(calladdr, 16)
- ref = m.group('ref')
- if '+' in ref:
+ ref = m.group("ref")
+ if "+" in ref:
# Inter-function jump.
continue
- elif insn.startswith('ld') or insn.startswith('st'):
+ elif insn.startswith("ld") or insn.startswith("st"):
# memory access
continue
- elif insn in ('rjmp', 'jmp', 'brne', 'brcs'):
+ elif insn in ("rjmp", "jmp", "brne", "brcs"):
# Tail call
cur.noteCall(insnaddr, calladdr, 0)
- elif insn in ('rcall', 'call'):
+ elif insn in ("rcall", "call"):
cur.noteCall(insnaddr, calladdr, stackusage + 2)
else:
print("unknown call", ref)
@@ -188,29 +200,29 @@ def main():
# Update for known indirect functions
funcsbyname = {}
for info in funcs.values():
- funcnameroot = info.funcname.split('.')[0]
+ funcnameroot = info.funcname.split(".")[0]
funcsbyname[funcnameroot] = info
- cmdfunc = funcsbyname.get('sched_main')
- command_index = funcsbyname.get('command_index')
+ cmdfunc = funcsbyname.get("sched_main")
+ command_index = funcsbyname.get("command_index")
if command_index is not None and cmdfunc is not None:
for line in datalines[command_index.funcaddr]:
parts = line.split()
if len(parts) < 9:
continue
- calladdr = int(parts[8]+parts[7], 16) * 2
+ calladdr = int(parts[8] + parts[7], 16) * 2
numparams = int(parts[2], 16)
stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4
cmdfunc.noteCall(0, calladdr, stackusage)
if len(parts) < 17:
continue
- calladdr = int(parts[16]+parts[15], 16) * 2
+ calladdr = int(parts[16] + parts[15], 16) * 2
numparams = int(parts[10], 16)
stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4
cmdfunc.noteCall(0, calladdr, stackusage)
- eventfunc = funcsbyname.get('__vector_13', funcsbyname.get('__vector_17'))
+ eventfunc = funcsbyname.get("__vector_13", funcsbyname.get("__vector_17"))
for funcnameroot, info in funcsbyname.items():
- if funcnameroot.endswith('_event') and eventfunc is not None:
- eventfunc.noteCall(0, info.funcaddr, eventfunc.basic_stack_usage+2)
+ if funcnameroot.endswith("_event") and eventfunc is not None:
+ eventfunc.noteCall(0, info.funcaddr, eventfunc.basic_stack_usage + 2)
# Calculate maxstackusage
for info in funcs.values():
@@ -227,17 +239,27 @@ def main():
yieldstr = ""
if info.max_yield_usage >= 0:
yieldstr = ",%d" % info.max_yield_usage
- print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage
- , info.max_stack_usage, yieldstr))
+ print(
+ "\n%s[%d,%d%s]:"
+ % (info.funcname, info.basic_stack_usage, info.max_stack_usage, yieldstr)
+ )
for insnaddr, calladdr, stackusage in info.called_funcs:
callinfo = funcs.get(calladdr, unknownfunc)
yieldstr = ""
if callinfo.max_yield_usage >= 0:
yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
- print(" %04s:%-40s [%d+%d,%d%s]" % (
- insnaddr, callinfo.funcname, stackusage
- , callinfo.basic_stack_usage
- , stackusage+callinfo.max_stack_usage, yieldstr))
+ print(
+ " %04s:%-40s [%d+%d,%d%s]"
+ % (
+ insnaddr,
+ callinfo.funcname,
+ stackusage,
+ callinfo.basic_stack_usage,
+ stackusage + callinfo.max_stack_usage,
+ yieldstr,
+ )
+ )
+
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/scripts/dump_mcu.py b/scripts/dump_mcu.py
index d9345a96..153962e5 100755
--- a/scripts/dump_mcu.py
+++ b/scripts/dump_mcu.py
@@ -10,8 +10,8 @@ import argparse
import os
import traceback
import logging
-KLIPPER_DIR = os.path.abspath(os.path.join(
- os.path.dirname(__file__), "../"))
+
+KLIPPER_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
sys.path.append(os.path.join(KLIPPER_DIR, "klippy"))
import reactor
import serialhdl
@@ -23,20 +23,25 @@ import clocksync
#
###########################################################
+
def output_line(msg):
sys.stdout.write("%s\n" % (msg,))
sys.stdout.flush()
+
def output(msg):
sys.stdout.write("%s" % (msg,))
sys.stdout.flush()
-DUMP_CMD="debug_read order=%d addr=%d"
-DUMP_RESP="debug_result"
+
+DUMP_CMD = "debug_read order=%d addr=%d"
+DUMP_RESP = "debug_result"
+
class MCUDumpError(Exception):
pass
+
class MCUDump:
def __init__(self, args):
self.reactor = reactor.Reactor()
@@ -49,9 +54,7 @@ class MCUDump:
self.read_start = int(args.read_start, 0)
self.read_length = int(args.read_length, 0)
except ValueError as e:
- raise MCUDumpError(
- "Error converting flash address: %s " % (str(e),)
- )
+ raise MCUDumpError("Error converting flash address: %s " % (str(e),))
if self.read_length <= 0:
raise MCUDumpError("Read count must be greater than 0")
self._serial = serialhdl.SerialReader(self.reactor)
@@ -66,7 +69,7 @@ class MCUDump:
self.reactor.register_callback(self._do_serial_connect)
curtime = self.reactor.monotonic()
while True:
- curtime = self.reactor.pause(curtime + 1.)
+ curtime = self.reactor.pause(curtime + 1.0)
output(".")
if self.connect_completion.test():
self.connected = self.connect_completion.wait()
@@ -83,16 +86,15 @@ class MCUDump:
output_line("Frequency: %s\n" % (freq,))
def _do_serial_connect(self, eventtime):
- endtime = eventtime + 60.
+ endtime = eventtime + 60.0
while True:
try:
if self.canbus_iface is not None:
self._serial.connect_canbus(
self.device, self.canbus_nodeid, self.canbus_iface
)
- elif (
- self.device.startswith("/dev/rpmsg_") or
- self.device.startswith("/tmp/")
+ elif self.device.startswith("/dev/rpmsg_") or self.device.startswith(
+ "/tmp/"
):
self._serial.connect_pipe(self.device)
else:
@@ -105,7 +107,7 @@ class MCUDump:
return
output("Connection Error, retrying..")
self._serial.disconnect()
- self.reactor.pause(curtime + 2.)
+ self.reactor.pause(curtime + 2.0)
else:
break
self.connect_completion.complete(True)
@@ -122,10 +124,7 @@ class MCUDump:
order = [2, 0, 1, 0][(addr | count) & 3]
bsize = 1 << order
# Query data from mcu
- output_line(
- "Reading %d bytes from flash, start address 0x%x\n"
- % (count, addr)
- )
+ output_line("Reading %d bytes from flash, start address 0x%x\n" % (count, addr))
output("[")
bytes_read = last_reported_pct = 0
vals = []
@@ -133,9 +132,9 @@ class MCUDump:
caddr = addr + (i << order)
cmd = DUMP_CMD % (order, caddr)
params = self._serial.send_with_response(cmd, DUMP_RESP)
- vals.append(params['val'])
+ vals.append(params["val"])
bytes_read += bsize
- pct = int(bytes_read / float(count) * 100 + .5)
+ pct = int(bytes_read / float(count) * 100 + 0.5)
diff = (pct - last_reported_pct) // 2
if diff:
last_reported_pct = pct
@@ -145,7 +144,7 @@ class MCUDump:
data = bytearray()
for val in vals:
for b in range(bsize):
- data.append((val >> (8 * b)) & 0xff)
+ data.append((val >> (8 * b)) & 0xFF)
data = data[:count]
with open(self.output_file, "wb") as f:
f.write(data)
@@ -164,29 +163,49 @@ class MCUDump:
self.disconnect()
self.reactor.finalize()
+
def main():
parser = argparse.ArgumentParser(description="MCU Flash Dump Utility")
parser.add_argument(
- "-b", "--baud", metavar="<baud rate>", type=int,
- default=250000, help="Baud Rate")
- parser.add_argument(
- "-c", "--canbus_iface", metavar="<canbus iface>", default=None,
- help="Use CAN bus interface; <device> is the chip UUID")
- parser.add_argument(
- "-i", "--canbus_nodeid", metavar="<canbus nodeid>", type=int,
- default=64, help="The CAN nodeid to use (default 64)")
+ "-b",
+ "--baud",
+ metavar="<baud rate>",
+ type=int,
+ default=250000,
+ help="Baud Rate",
+ )
parser.add_argument(
- "-s", "--read_start", metavar="<read start>", default="0x0",
- help="Flash address to start reading")
+ "-c",
+ "--canbus_iface",
+ metavar="<canbus iface>",
+ default=None,
+ help="Use CAN bus interface; <device> is the chip UUID",
+ )
parser.add_argument(
- "-l", "--read_length", metavar="<read length>", default="0x400",
- help="Number of bytes to read")
+ "-i",
+ "--canbus_nodeid",
+ metavar="<canbus nodeid>",
+ type=int,
+ default=64,
+ help="The CAN nodeid to use (default 64)",
+ )
parser.add_argument(
- "device", metavar="<device>", help="Device Serial Port")
+ "-s",
+ "--read_start",
+ metavar="<read start>",
+ default="0x0",
+ help="Flash address to start reading",
+ )
parser.add_argument(
- "outfile", metavar="<outfile>",
- help="Path to output file")
+ "-l",
+ "--read_length",
+ metavar="<read length>",
+ default="0x400",
+ help="Number of bytes to read",
+ )
+ parser.add_argument("device", metavar="<device>", help="Device Serial Port")
+ parser.add_argument("outfile", metavar="<outfile>", help="Path to output file")
args = parser.parse_args()
logging.basicConfig(level=logging.CRITICAL)
try:
diff --git a/scripts/flash-ar100.py b/scripts/flash-ar100.py
index 33198e23..0896ae07 100755
--- a/scripts/flash-ar100.py
+++ b/scripts/flash-ar100.py
@@ -21,11 +21,11 @@ R_CPU_CFG_SIZE = R_CPU_CFG_PAGE_LIMIT - R_CPU_CFG_PAGE_BASE
R_CPU_CFG_OFFSET = 0xC00
R_CPU_CLK_OFFSET = 0x400
-parser = argparse.ArgumentParser(description='Flash and reset SRAM A2 of A64')
-parser.add_argument('filename', nargs='?', help='binary file to write')
-parser.add_argument('--reset', action='store_true', help='reset the AR100')
-parser.add_argument('--halt', action='store_true', help='Halt the AR100')
-parser.add_argument('--bl31', action='store_true', help='write bl31')
+parser = argparse.ArgumentParser(description="Flash and reset SRAM A2 of A64")
+parser.add_argument("filename", nargs="?", help="binary file to write")
+parser.add_argument("--reset", action="store_true", help="reset the AR100")
+parser.add_argument("--halt", action="store_true", help="Halt the AR100")
+parser.add_argument("--bl31", action="store_true", help="write bl31")
args = parser.parse_args()
@@ -33,21 +33,20 @@ args = parser.parse_args()
def write_exception_vectors():
print("Writing exception vectors")
with open("/dev/mem", "w+b") as f:
- exc = mmap.mmap(f.fileno(),
- length=EXCEPTIONS_SIZE,
- offset=EXCEPTIONS_BASE)
+ exc = mmap.mmap(f.fileno(), length=EXCEPTIONS_SIZE, offset=EXCEPTIONS_BASE)
for i in range(NR_OF_EXCEPTIONS):
add = i * 0x100
- exc[add:add + 4] = ((EXCEPTIONS_JUMP - add) >> 2).to_bytes(
- 4, byteorder='little')
+ exc[add : add + 4] = ((EXCEPTIONS_JUMP - add) >> 2).to_bytes(
+ 4, byteorder="little"
+ )
exc.close()
def assert_deassert_reset(ass):
with open("/dev/mem", "w+b") as f:
- r_cpucfg = mmap.mmap(f.fileno(),
- length=R_CPU_CFG_SIZE,
- offset=R_CPU_CFG_PAGE_BASE)
+ r_cpucfg = mmap.mmap(
+ f.fileno(), length=R_CPU_CFG_SIZE, offset=R_CPU_CFG_PAGE_BASE
+ )
if ass:
r_cpucfg[R_CPU_CFG_OFFSET] &= ~0x01
if r_cpucfg[R_CPU_CFG_OFFSET] & 0x01:
@@ -68,7 +67,7 @@ def write_file(filename):
print("Writing file to SRAM A2")
with open("/dev/mem", "w+b") as f:
sram_a2 = mmap.mmap(f.fileno(), length=FW_SIZE, offset=FW_BASE)
- sram_a2[0:len(data)] = data
+ sram_a2[0 : len(data)] = data
sram_a2.close()
diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py
index e290f7f3..99e291fc 100755
--- a/scripts/flash_usb.py
+++ b/scripts/flash_usb.py
@@ -6,35 +6,39 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, re, subprocess, optparse, time, fcntl, termios, struct
+
class error(Exception):
pass
+
# Attempt to enter bootloader via 1200 baud request
def enter_bootloader(device):
try:
- f = open(device, 'rb')
+ f = open(device, "rb")
fd = f.fileno()
- fcntl.ioctl(fd, termios.TIOCMBIS, struct.pack('I', termios.TIOCM_DTR))
+ fcntl.ioctl(fd, termios.TIOCMBIS, struct.pack("I", termios.TIOCM_DTR))
t = termios.tcgetattr(fd)
t[4] = t[5] = termios.B1200
sys.stderr.write("Entering bootloader on %s\n" % (device,))
termios.tcsetattr(fd, termios.TCSANOW, t)
- fcntl.ioctl(fd, termios.TIOCMBIC, struct.pack('I', termios.TIOCM_DTR))
+ fcntl.ioctl(fd, termios.TIOCMBIC, struct.pack("I", termios.TIOCM_DTR))
f.close()
except (IOError, OSError) as e:
pass
+
# Translate a serial device name to a stable serial name in /dev/serial/by-path/
def translate_serial_to_tty(device):
ttyname = os.path.realpath(device)
- if not os.path.exists('/dev/serial/by-path/'):
+ if not os.path.exists("/dev/serial/by-path/"):
raise error("Unable to find serial 'by-path' folder")
- for fname in os.listdir('/dev/serial/by-path/'):
- fname = '/dev/serial/by-path/' + fname
+ for fname in os.listdir("/dev/serial/by-path/"):
+ fname = "/dev/serial/by-path/" + fname
if os.path.realpath(fname) == ttyname:
return ttyname, fname
return ttyname, ttyname
+
# Translate a serial device name to a usb path (suitable for dfu-util)
def translate_serial_to_usb_path(device):
realdev = os.path.realpath(device)
@@ -50,9 +54,10 @@ def translate_serial_to_usb_path(device):
devpath = os.path.realpath("/sys/class/tty/%s/device" % (fname,))
return m.group("path"), devpath
+
# Wait for a given path to appear
def wait_path(path, alt_path=None):
- time.sleep(.100)
+ time.sleep(0.100)
start_alt_path = None
end_time = time.time() + 4.0
while 1:
@@ -67,13 +72,14 @@ def wait_path(path, alt_path=None):
start_alt_path = cur_time
continue
if cur_time >= start_alt_path + 0.300:
- sys.stderr.write("Device reconnect on alt path %s\n" % (
- alt_path,))
+ sys.stderr.write("Device reconnect on alt path %s\n" % (alt_path,))
return alt_path
if cur_time > end_time:
return path
-CANBOOT_ID ="1d50:6177"
+
+CANBOOT_ID = "1d50:6177"
+
def detect_canboot(devpath):
usbdir = os.path.dirname(devpath)
@@ -87,6 +93,7 @@ def detect_canboot(devpath):
usbid = "%s:%s" % (vid, pid)
return usbid == CANBOOT_ID
+
def call_flashcan(device, binfile):
try:
import serial
@@ -97,18 +104,19 @@ def call_flashcan(device, binfile):
" %s -m pip install pyserial\n\n" % (sys.executable,)
)
sys.exit(-1)
- args = [sys.executable, "lib/canboot/flash_can.py", "-d",
- device, "-f", binfile]
- sys.stderr.write(" ".join(args) + '\n\n')
+ args = [sys.executable, "lib/canboot/flash_can.py", "-d", device, "-f", binfile]
+ sys.stderr.write(" ".join(args) + "\n\n")
res = subprocess.call(args)
if res != 0:
sys.stderr.write("Error running flash_can.py\n")
sys.exit(-1)
+
def flash_canboot(options, binfile):
ttyname, pathname = translate_serial_to_tty(options.device)
call_flashcan(pathname, binfile)
+
# Flash via a call to bossac
def flash_bossac(device, binfile, extra_flags=[]):
ttyname, pathname = translate_serial_to_tty(device)
@@ -116,7 +124,7 @@ def flash_bossac(device, binfile, extra_flags=[]):
pathname = wait_path(pathname, ttyname)
baseargs = ["lib/bossac/bin/bossac", "-U", "-p", pathname]
args = baseargs + extra_flags + ["-w", binfile, "-v"]
- sys.stderr.write(" ".join(args) + '\n\n')
+ sys.stderr.write(" ".join(args) + "\n\n")
res = subprocess.call(args)
if res != 0:
raise error("Error running bossac")
@@ -130,21 +138,23 @@ def flash_bossac(device, binfile, extra_flags=[]):
except subprocess.CalledProcessError as e:
pass
+
# Invoke the dfu-util program
def call_dfuutil(flags, binfile, sudo):
args = ["dfu-util"] + flags + ["-D", binfile]
if sudo:
args.insert(0, "sudo")
- sys.stderr.write(" ".join(args) + '\n\n')
+ sys.stderr.write(" ".join(args) + "\n\n")
res = subprocess.call(args)
if res != 0:
raise error("Error running dfu-util")
+
# Flash via a call to dfu-util
def flash_dfuutil(device, binfile, extra_flags=[], sudo=True):
hexfmt_r = re.compile(r"^[a-fA-F0-9]{4}:[a-fA-F0-9]{4}$")
if hexfmt_r.match(device.strip()):
- call_dfuutil(["-d", ","+device.strip()] + extra_flags, binfile, sudo)
+ call_dfuutil(["-d", "," + device.strip()] + extra_flags, binfile, sudo)
return
ttyname, serbypath = translate_serial_to_tty(device)
buspath, devpath = translate_serial_to_usb_path(device)
@@ -155,15 +165,17 @@ def flash_dfuutil(device, binfile, extra_flags=[], sudo=True):
else:
call_dfuutil(["-p", buspath] + extra_flags, binfile, sudo)
+
def call_hidflash(binfile, sudo):
args = ["lib/hidflash/hid-flash", binfile]
if sudo:
args.insert(0, "sudo")
- sys.stderr.write(" ".join(args) + '\n\n')
+ sys.stderr.write(" ".join(args) + "\n\n")
res = subprocess.call(args)
if res != 0:
raise error("Error running hid-flash")
+
# Flash via call to hid-flash
def flash_hidflash(device, binfile, sudo=True):
hexfmt_r = re.compile(r"^[a-fA-F0-9]{4}:[a-fA-F0-9]{4}$")
@@ -179,6 +191,7 @@ def flash_hidflash(device, binfile, sudo=True):
else:
call_hidflash(binfile, sudo)
+
# Call Klipper modified "picoboot"
def call_picoboot(bus, addr, binfile, sudo):
args = ["lib/rp2040_flash/rp2040_flash", binfile]
@@ -186,11 +199,12 @@ def call_picoboot(bus, addr, binfile, sudo):
args.extend([bus, addr])
if sudo:
args.insert(0, "sudo")
- sys.stderr.write(" ".join(args) + '\n\n')
+ sys.stderr.write(" ".join(args) + "\n\n")
res = subprocess.call(args)
if res != 0:
raise error("Error running rp2040_flash")
+
# Flash via Klipper modified "picoboot"
def flash_picoboot(device, binfile, sudo):
ttyname, serbypath = translate_serial_to_tty(device)
@@ -213,31 +227,32 @@ def flash_picoboot(device, binfile, sudo):
# Device specific helpers
######################################################################
+
def flash_atsam3(options, binfile):
try:
flash_bossac(options.device, binfile, ["-e", "-b"])
except error as e:
- sys.stderr.write("Failed to flash to %s: %s\n" % (
- options.device, str(e)))
+ sys.stderr.write("Failed to flash to %s: %s\n" % (options.device, str(e)))
sys.exit(-1)
+
def flash_atsam4(options, binfile):
try:
flash_bossac(options.device, binfile, ["-e"])
except error as e:
- sys.stderr.write("Failed to flash to %s: %s\n" % (
- options.device, str(e)))
+ sys.stderr.write("Failed to flash to %s: %s\n" % (options.device, str(e)))
sys.exit(-1)
+
def flash_atsamd(options, binfile):
extra_flags = ["--offset=0x%x" % (options.start,), "-b", "-R"]
try:
flash_bossac(options.device, binfile, extra_flags)
except error as e:
- sys.stderr.write("Failed to flash to %s: %s\n" % (
- options.device, str(e)))
+ sys.stderr.write("Failed to flash to %s: %s\n" % (options.device, str(e)))
sys.exit(-1)
+
SMOOTHIE_HELP = """
Failed to flash to %s: %s
@@ -256,6 +271,7 @@ and then restart the Smoothieboard with that SD card.
"""
+
def flash_lpc176x(options, binfile):
try:
flash_dfuutil(options.device, binfile, [], options.sudo)
@@ -263,6 +279,7 @@ def flash_lpc176x(options, binfile):
sys.stderr.write(SMOOTHIE_HELP % (options.device, str(e)))
sys.exit(-1)
+
STM32F1_HELP = """
Failed to flash to %s: %s
@@ -277,18 +294,18 @@ If attempting to flash via 3.3V serial, then use:
"""
+
def flash_stm32f1(options, binfile):
try:
if options.start == 0x8000800:
flash_hidflash(options.device, binfile, options.sudo)
else:
- flash_dfuutil(options.device, binfile, ["-R", "-a", "2"],
- options.sudo)
+ flash_dfuutil(options.device, binfile, ["-R", "-a", "2"], options.sudo)
except error as e:
- sys.stderr.write(STM32F1_HELP % (
- options.device, str(e), options.device))
+ sys.stderr.write(STM32F1_HELP % (options.device, str(e), options.device))
sys.exit(-1)
+
STM32F4_HELP = """
Failed to flash to %s: %s
@@ -303,19 +320,21 @@ If attempting to flash via 3.3V serial, then use:
"""
+
def flash_stm32f4(options, binfile):
start = "0x%x:leave" % (options.start,)
try:
if options.start == 0x8004000:
flash_hidflash(options.device, binfile, options.sudo)
else:
- flash_dfuutil(options.device, binfile,
- ["-R", "-a", "0", "-s", start], options.sudo)
+ flash_dfuutil(
+ options.device, binfile, ["-R", "-a", "0", "-s", start], options.sudo
+ )
except error as e:
- sys.stderr.write(STM32F4_HELP % (
- options.device, str(e), options.device))
+ sys.stderr.write(STM32F4_HELP % (options.device, str(e), options.device))
sys.exit(-1)
+
RP2040_HELP = """
Failed to flash to %s: %s
@@ -329,9 +348,10 @@ device as a usb drive, and copy klipper.uf2 to the device.
"""
+
def flash_rp2040(options, binfile):
rawdev = "2e8a:0003"
- if options.mcutype == 'rp2350':
+ if options.mcutype == "rp2350":
rawdev = "2e8a:000f"
try:
if options.device.lower() == rawdev:
@@ -342,15 +362,25 @@ def flash_rp2040(options, binfile):
sys.stderr.write(RP2040_HELP % (options.device, str(e), rawdev))
sys.exit(-1)
+
MCUTYPES = {
- 'sam3': flash_atsam3, 'sam4': flash_atsam4, 'same70': flash_atsam4,
- 'samd': flash_atsamd, 'same5': flash_atsamd,
- 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1,
- 'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4,
- 'stm32f070': flash_stm32f4, 'stm32f072': flash_stm32f4,
- 'stm32g0b1': flash_stm32f4, 'stm32f7': flash_stm32f4,
- 'stm32h7': flash_stm32f4, 'stm32l4': flash_stm32f4,
- 'stm32g4': flash_stm32f4, 'rp2': flash_rp2040,
+ "sam3": flash_atsam3,
+ "sam4": flash_atsam4,
+ "same70": flash_atsam4,
+ "samd": flash_atsamd,
+ "same5": flash_atsamd,
+ "lpc176": flash_lpc176x,
+ "stm32f103": flash_stm32f1,
+ "stm32f4": flash_stm32f4,
+ "stm32f042": flash_stm32f4,
+ "stm32f070": flash_stm32f4,
+ "stm32f072": flash_stm32f4,
+ "stm32g0b1": flash_stm32f4,
+ "stm32f7": flash_stm32f4,
+ "stm32h7": flash_stm32f4,
+ "stm32l4": flash_stm32f4,
+ "stm32g4": flash_stm32f4,
+ "rp2": flash_rp2040,
}
@@ -358,17 +388,26 @@ MCUTYPES = {
# Startup
######################################################################
+
def main():
usage = "%prog [options] -t <type> -d <device> <klipper.bin>"
opts = optparse.OptionParser(usage)
- opts.add_option("-t", "--type", type="string", dest="mcutype",
- help="micro-controller type")
- opts.add_option("-d", "--device", type="string", dest="device",
- help="serial port device")
- opts.add_option("-s", "--start", type="int", dest="start",
- help="start address in flash")
- opts.add_option("--no-sudo", action="store_false", dest="sudo",
- default=True, help="do not run sudo")
+ opts.add_option(
+ "-t", "--type", type="string", dest="mcutype", help="micro-controller type"
+ )
+ opts.add_option(
+ "-d", "--device", type="string", dest="device", help="serial port device"
+ )
+ opts.add_option(
+ "-s", "--start", type="int", dest="start", help="start address in flash"
+ )
+ opts.add_option(
+ "--no-sudo",
+ action="store_false",
+ dest="sudo",
+ default=True,
+ help="do not run sudo",
+ )
options, args = opts.parse_args()
if len(args) != 1:
opts.error("Incorrect number of arguments")
@@ -379,12 +418,12 @@ def main():
flash_func = func
break
if flash_func is None:
- opts.error("USB flashing is not supported for MCU '%s'"
- % (options.mcutype,))
+ opts.error("USB flashing is not supported for MCU '%s'" % (options.mcutype,))
if not options.device:
sys.stderr.write("\nPlease specify FLASH_DEVICE\n\n")
sys.exit(-1)
flash_func(options, args[0])
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/graph_accelerometer.py b/scripts/graph_accelerometer.py
index 84b31311..7286c041 100755
--- a/scripts/graph_accelerometer.py
+++ b/scripts/graph_accelerometer.py
@@ -8,59 +8,73 @@
import importlib, optparse, os, sys
from textwrap import wrap
import numpy as np, matplotlib
-sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)),
- '..', 'klippy'))
-shaper_calibrate = importlib.import_module('.shaper_calibrate', 'extras')
-MAX_TITLE_LENGTH=65
+sys.path.append(
+ os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "klippy")
+)
+shaper_calibrate = importlib.import_module(".shaper_calibrate", "extras")
+
+MAX_TITLE_LENGTH = 65
+
def parse_log(logname, opts):
with open(logname) as f:
for header in f:
- if header.startswith('#'):
+ if header.startswith("#"):
continue
- if header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'):
+ if header.startswith("freq,psd_x,psd_y,psd_z,psd_xyz"):
# Processed power spectral density file
break
# Raw accelerometer data
- return np.loadtxt(logname, comments='#', delimiter=',')
+ return np.loadtxt(logname, comments="#", delimiter=",")
# Parse power spectral density data
- data = np.loadtxt(logname, skiprows=1, comments='#', delimiter=',')
+ data = np.loadtxt(logname, skiprows=1, comments="#", delimiter=",")
calibration_data = shaper_calibrate.CalibrationData(
- freq_bins=data[:,0], psd_sum=data[:,4],
- psd_x=data[:,1], psd_y=data[:,2], psd_z=data[:,3])
+ freq_bins=data[:, 0],
+ psd_sum=data[:, 4],
+ psd_x=data[:, 1],
+ psd_y=data[:, 2],
+ psd_z=data[:, 3],
+ )
calibration_data.set_numpy(np)
return calibration_data
+
######################################################################
# Raw accelerometer graphing
######################################################################
+
def plot_accel(datas, lognames):
fig, axes = matplotlib.pyplot.subplots(nrows=3, sharex=True)
- axes[0].set_title("\n".join(wrap(
- "Accelerometer data (%s)" % (', '.join(lognames)), MAX_TITLE_LENGTH)))
- axis_names = ['x', 'y', 'z']
+ axes[0].set_title(
+ "\n".join(
+ wrap("Accelerometer data (%s)" % (", ".join(lognames)), MAX_TITLE_LENGTH)
+ )
+ )
+ axis_names = ["x", "y", "z"]
for data, logname in zip(datas, lognames):
if isinstance(data, shaper_calibrate.CalibrationData):
- raise error("Cannot plot raw accelerometer data using the processed"
- " resonances, raw_data input is required")
+ raise error(
+ "Cannot plot raw accelerometer data using the processed"
+ " resonances, raw_data input is required"
+ )
first_time = data[0, 0]
- times = data[:,0] - first_time
+ times = data[:, 0] - first_time
for i in range(len(axis_names)):
- avg = data[:,i+1].mean()
- adata = data[:,i+1] - data[:,i+1].mean()
+ avg = data[:, i + 1].mean()
+ adata = data[:, i + 1] - data[:, i + 1].mean()
ax = axes[i]
- label = '\n'.join(wrap(logname, 60)) + ' (%+.3f mm/s^2)' % (-avg,)
+ label = "\n".join(wrap(logname, 60)) + " (%+.3f mm/s^2)" % (-avg,)
ax.plot(times, adata, alpha=0.8, label=label)
- axes[-1].set_xlabel('Time (s)')
+ axes[-1].set_xlabel("Time (s)")
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
+ fontP.set_size("x-small")
for i in range(len(axis_names)):
ax = axes[i]
ax.grid(True)
- ax.legend(loc='best', prop=fontP)
- ax.set_ylabel('%s accel' % (axis_names[i],))
+ ax.legend(loc="best", prop=fontP)
+ ax.set_ylabel("%s accel" % (axis_names[i],))
fig.tight_layout()
return fig
@@ -69,6 +83,7 @@ def plot_accel(datas, lognames):
# Frequency graphing
######################################################################
+
# Calculate estimated "power spectral density"
def calc_freq_response(data, max_freq):
if isinstance(data, shaper_calibrate.CalibrationData):
@@ -76,29 +91,41 @@ def calc_freq_response(data, max_freq):
helper = shaper_calibrate.ShaperCalibrate(printer=None)
return helper.process_accelerometer_data(data)
+
def calc_specgram(data, axis):
if isinstance(data, shaper_calibrate.CalibrationData):
- raise error("Cannot calculate the spectrogram using the processed"
- " resonances, raw_data input is required")
+ raise error(
+ "Cannot calculate the spectrogram using the processed"
+ " resonances, raw_data input is required"
+ )
N = data.shape[0]
- Fs = N / (data[-1,0] - data[0,0])
+ Fs = N / (data[-1, 0] - data[0, 0])
# Round up to a power of 2 for faster FFT
- M = 1 << int(.5 * Fs - 1).bit_length()
- window = np.kaiser(M, 6.)
+ M = 1 << int(0.5 * Fs - 1).bit_length()
+ window = np.kaiser(M, 6.0)
+
def _specgram(x):
return matplotlib.mlab.specgram(
- x, Fs=Fs, NFFT=M, noverlap=M//2, window=window,
- mode='psd', detrend='mean', scale_by_freq=False)
+ x,
+ Fs=Fs,
+ NFFT=M,
+ noverlap=M // 2,
+ window=window,
+ mode="psd",
+ detrend="mean",
+ scale_by_freq=False,
+ )
- d = {'x': data[:,1], 'y': data[:,2], 'z': data[:,3]}
- if axis != 'all':
+ d = {"x": data[:, 1], "y": data[:, 2], "z": data[:, 3]}
+ if axis != "all":
pdata, bins, t = _specgram(d[axis])
else:
- pdata, bins, t = _specgram(d['x'])
- for ax in 'yz':
+ pdata, bins, t = _specgram(d["x"])
+ for ax in "yz":
pdata += _specgram(d[ax])[0]
return pdata, bins, t
+
def plot_frequency(datas, lognames, max_freq):
calibration_data = calc_freq_response(datas[0], max_freq)
for data in datas[1:]:
@@ -111,33 +138,37 @@ def plot_frequency(datas, lognames, max_freq):
freqs = freqs[freqs <= max_freq]
fig, ax = matplotlib.pyplot.subplots()
- ax.set_title("\n".join(wrap(
- "Frequency response (%s)" % (', '.join(lognames)), MAX_TITLE_LENGTH)))
- ax.set_xlabel('Frequency (Hz)')
- ax.set_ylabel('Power spectral density')
+ ax.set_title(
+ "\n".join(
+ wrap("Frequency response (%s)" % (", ".join(lognames)), MAX_TITLE_LENGTH)
+ )
+ )
+ ax.set_xlabel("Frequency (Hz)")
+ ax.set_ylabel("Power spectral density")
- ax.plot(freqs, psd, label='X+Y+Z', alpha=0.6)
- ax.plot(freqs, px, label='X', alpha=0.6)
- ax.plot(freqs, py, label='Y', alpha=0.6)
- ax.plot(freqs, pz, label='Z', alpha=0.6)
+ ax.plot(freqs, psd, label="X+Y+Z", alpha=0.6)
+ ax.plot(freqs, px, label="X", alpha=0.6)
+ ax.plot(freqs, py, label="Y", alpha=0.6)
+ ax.plot(freqs, pz, label="Z", alpha=0.6)
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
- ax.grid(which='major', color='grey')
- ax.grid(which='minor', color='lightgrey')
- ax.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
+ ax.grid(which="major", color="grey")
+ ax.grid(which="minor", color="lightgrey")
+ ax.ticklabel_format(axis="y", style="scientific", scilimits=(0, 0))
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax.legend(loc='best', prop=fontP)
+ fontP.set_size("x-small")
+ ax.legend(loc="best", prop=fontP)
fig.tight_layout()
return fig
+
def plot_compare_frequency(datas, lognames, max_freq, axis):
fig, ax = matplotlib.pyplot.subplots()
- ax.set_title('Frequency responses comparison')
- ax.set_xlabel('Frequency (Hz)')
- ax.set_ylabel('Power spectral density')
+ ax.set_title("Frequency responses comparison")
+ ax.set_xlabel("Frequency (Hz)")
+ ax.set_ylabel("Power spectral density")
for data, logname in zip(datas, lognames):
calibration_data = calc_freq_response(data, max_freq)
@@ -148,32 +179,36 @@ def plot_compare_frequency(datas, lognames, max_freq, axis):
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
- ax.grid(which='major', color='grey')
- ax.grid(which='minor', color='lightgrey')
+ ax.grid(which="major", color="grey")
+ ax.grid(which="minor", color="lightgrey")
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax.legend(loc='best', prop=fontP)
+ fontP.set_size("x-small")
+ ax.legend(loc="best", prop=fontP)
fig.tight_layout()
return fig
+
# Plot data in a "spectrogram colormap"
def plot_specgram(data, logname, max_freq, axis):
pdata, bins, t = calc_specgram(data, axis)
fig, ax = matplotlib.pyplot.subplots()
- ax.set_title("\n".join(wrap("Spectrogram %s (%s)" % (axis, logname),
- MAX_TITLE_LENGTH)))
+ ax.set_title(
+ "\n".join(wrap("Spectrogram %s (%s)" % (axis, logname), MAX_TITLE_LENGTH))
+ )
ax.pcolormesh(t, bins, pdata, norm=matplotlib.colors.LogNorm())
- ax.set_ylim([0., max_freq])
- ax.set_ylabel('frequency (hz)')
- ax.set_xlabel('Time (s)')
+ ax.set_ylim([0.0, max_freq])
+ ax.set_ylabel("frequency (hz)")
+ ax.set_xlabel("Time (s)")
fig.tight_layout()
return fig
+
######################################################################
# CSV output
######################################################################
+
def write_frequency_response(datas, output):
helper = shaper_calibrate.ShaperCalibrate(printer=None)
calibration_data = helper.process_accelerometer_data(datas[0])
@@ -181,6 +216,7 @@ def write_frequency_response(datas, output):
calibration_data.add_data(helper.process_accelerometer_data(data))
helper.save_calibration_data(output, calibration_data)
+
def write_specgram(psd, freq_bins, time, output):
M = freq_bins.shape[0]
with open(output, "w") as csvfile:
@@ -190,46 +226,76 @@ def write_specgram(psd, freq_bins, time, output):
csvfile.write("\n")
for i in range(M):
csvfile.write("%.1f" % (freq_bins[i],))
- for value in psd[i,:]:
+ for value in psd[i, :]:
csvfile.write(",%.6e" % (value,))
csvfile.write("\n")
+
######################################################################
# Startup
######################################################################
+
def is_csv_output(output):
- return output and os.path.splitext(output)[1].lower() == '.csv'
+ return output and os.path.splitext(output)[1].lower() == ".csv"
+
def setup_matplotlib(output):
global matplotlib
if is_csv_output(output):
# Only mlab may be necessary with CSV output
import matplotlib.mlab
+
return
if output:
- matplotlib.rcParams.update({'figure.autolayout': True})
- matplotlib.use('Agg')
+ matplotlib.rcParams.update({"figure.autolayout": True})
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def main():
# Parse command-line arguments
usage = "%prog [options] <raw logs>"
opts = optparse.OptionParser(usage)
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
- opts.add_option("-f", "--max_freq", type="float", default=200.,
- help="maximum frequency to graph")
- opts.add_option("-r", "--raw", action="store_true",
- help="graph raw accelerometer data")
- opts.add_option("-c", "--compare", action="store_true",
- help="graph comparison of power spectral density "
- "between different accelerometer data files")
- opts.add_option("-s", "--specgram", action="store_true",
- help="graph spectrogram of accelerometer data")
- opts.add_option("-a", type="string", dest="axis", default="all",
- help="axis to graph (one of 'all', 'x', 'y', or 'z')")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
+ opts.add_option(
+ "-f",
+ "--max_freq",
+ type="float",
+ default=200.0,
+ help="maximum frequency to graph",
+ )
+ opts.add_option(
+ "-r", "--raw", action="store_true", help="graph raw accelerometer data"
+ )
+ opts.add_option(
+ "-c",
+ "--compare",
+ action="store_true",
+ help="graph comparison of power spectral density "
+ "between different accelerometer data files",
+ )
+ opts.add_option(
+ "-s",
+ "--specgram",
+ action="store_true",
+ help="graph spectrogram of accelerometer data",
+ )
+ opts.add_option(
+ "-a",
+ type="string",
+ dest="axis",
+ default="all",
+ help="axis to graph (one of 'all', 'x', 'y', or 'z')",
+ )
options, args = opts.parse_args()
if len(args) < 1:
opts.error("Incorrect number of arguments")
@@ -261,8 +327,7 @@ def main():
opts.error("Only 1 input is supported in specgram mode")
fig = plot_specgram(datas[0], args[0], options.max_freq, options.axis)
elif options.compare:
- fig = plot_compare_frequency(datas, args, options.max_freq,
- options.axis)
+ fig = plot_compare_frequency(datas, args, options.max_freq, options.axis)
else:
fig = plot_frequency(datas, args, options.max_freq)
@@ -273,5 +338,6 @@ def main():
fig.set_size_inches(8, 6)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/graph_extruder.py b/scripts/graph_extruder.py
index 6f31ed3c..7475c6aa 100755
--- a/scripts/graph_extruder.py
+++ b/scripts/graph_extruder.py
@@ -7,8 +7,8 @@
import math, optparse, datetime
import matplotlib
-SEG_TIME = .000100
-INV_SEG_TIME = 1. / SEG_TIME
+SEG_TIME = 0.000100
+INV_SEG_TIME = 1.0 / SEG_TIME
######################################################################
@@ -17,27 +17,32 @@ INV_SEG_TIME = 1. / SEG_TIME
# List of moves: [(start_v, end_v, move_t), ...]
Moves = [
- (0., 0., .100),
- (0., 100., None), (100., 100., .200), (100., 60., None),
- (60., 100., None), (100., 100., .200), (100., 0., None),
- (0., 0., .300)
+ (0.0, 0.0, 0.100),
+ (0.0, 100.0, None),
+ (100.0, 100.0, 0.200),
+ (100.0, 60.0, None),
+ (60.0, 100.0, None),
+ (100.0, 100.0, 0.200),
+ (100.0, 0.0, None),
+ (0.0, 0.0, 0.300),
]
-EXTRUDE_R = (.4 * .4 * .75) / (math.pi * (1.75 / 2.)**2)
-ACCEL = 3000. * EXTRUDE_R
+EXTRUDE_R = (0.4 * 0.4 * 0.75) / (math.pi * (1.75 / 2.0) ** 2)
+ACCEL = 3000.0 * EXTRUDE_R
+
def gen_positions():
out = []
- start_d = start_t = t = 0.
+ start_d = start_t = t = 0.0
for start_v, end_v, move_t in Moves:
start_v *= EXTRUDE_R
end_v *= EXTRUDE_R
if move_t is None:
move_t = abs(end_v - start_v) / ACCEL
- half_accel = 0.
+ half_accel = 0.0
if end_v > start_v:
- half_accel = .5 * ACCEL
+ half_accel = 0.5 * ACCEL
elif start_v > end_v:
- half_accel = -.5 * ACCEL
+ half_accel = -0.5 * ACCEL
end_t = start_t + move_t
while t <= end_t:
rel_t = t - start_t
@@ -54,15 +59,18 @@ def gen_positions():
MARGIN_TIME = 0.050
+
def time_to_index(t):
- return int(t * INV_SEG_TIME + .5)
+ return int(t * INV_SEG_TIME + 0.5)
+
def indexes(positions):
drop = time_to_index(MARGIN_TIME)
- return range(drop, len(positions)-drop)
+ return range(drop, len(positions) - drop)
+
def trim_lists(*lists):
- keep = len(lists[0]) - time_to_index(2. * MARGIN_TIME)
+ keep = len(lists[0]) - time_to_index(2.0 * MARGIN_TIME)
for l in lists:
del l[keep:]
@@ -71,36 +79,42 @@ def trim_lists(*lists):
# Common data filters
######################################################################
+
# Generate estimated first order derivative
def gen_deriv(data):
- return [0.] + [(data[i+1] - data[i]) * INV_SEG_TIME
- for i in range(len(data)-1)]
+ return [0.0] + [
+ (data[i + 1] - data[i]) * INV_SEG_TIME for i in range(len(data) - 1)
+ ]
+
# Simple average between two points smooth_time away
def calc_average(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- out[i] = .5 * (positions[i-offset] + positions[i+offset])
+ out[i] = 0.5 * (positions[i - offset] + positions[i + offset])
return out
+
# Average (via integration) of smooth_time range
def calc_smooth(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- weight = 1. / (2*offset - 1)
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ weight = 1.0 / (2 * offset - 1)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- out[i] = sum(positions[i-offset+1:i+offset]) * weight
+ out[i] = sum(positions[i - offset + 1 : i + offset]) * weight
return out
+
# Time weighted average (via integration) of smooth_time range
def calc_weighted(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- weight = 1. / offset**2
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ weight = 1.0 / offset**2
+ out = [0.0] * len(positions)
for i in indexes(positions):
- weighted_data = [positions[j] * (offset - abs(j-i))
- for j in range(i-offset, i+offset)]
+ weighted_data = [
+ positions[j] * (offset - abs(j - i)) for j in range(i - offset, i + offset)
+ ]
out[i] = sum(weighted_data) * weight
return out
@@ -109,17 +123,19 @@ def calc_weighted(positions, smooth_time):
# Pressure advance
######################################################################
-SMOOTH_TIME = .040
-PRESSURE_ADVANCE = .045
+SMOOTH_TIME = 0.040
+PRESSURE_ADVANCE = 0.045
+
# Calculate raw pressure advance positions
def calc_pa_raw(positions):
pa = PRESSURE_ADVANCE * INV_SEG_TIME
- out = [0.] * len(positions)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- out[i] = positions[i] + pa * (positions[i+1] - positions[i])
+ out[i] = positions[i] + pa * (positions[i + 1] - positions[i])
return out
+
# Pressure advance after smoothing
def calc_pa(positions):
return calc_weighted(calc_pa_raw(positions), SMOOTH_TIME)
@@ -129,6 +145,7 @@ def calc_pa(positions):
# Plotting and startup
######################################################################
+
def plot_motion():
# Nominal motion
positions = gen_positions()
@@ -142,38 +159,53 @@ def plot_motion():
sm_velocities = gen_deriv(sm_positions)
# Build plot
times = [SEG_TIME * i for i in range(len(positions))]
- trim_lists(times, velocities, accels,
- pa_positions, pa_velocities,
- sm_positions, sm_velocities)
+ trim_lists(
+ times,
+ velocities,
+ accels,
+ pa_positions,
+ pa_velocities,
+ sm_positions,
+ sm_velocities,
+ )
fig, ax1 = matplotlib.pyplot.subplots(nrows=1, sharex=True)
ax1.set_title("Extruder Velocity")
- ax1.set_ylabel('Velocity (mm/s)')
- pa_plot, = ax1.plot(times, pa_velocities, 'r',
- label='Pressure Advance', alpha=0.3)
- nom_plot, = ax1.plot(times, velocities, 'black', label='Nominal')
- sm_plot, = ax1.plot(times, sm_velocities, 'g', label='Smooth PA', alpha=0.9)
+ ax1.set_ylabel("Velocity (mm/s)")
+ (pa_plot,) = ax1.plot(
+ times, pa_velocities, "r", label="Pressure Advance", alpha=0.3
+ )
+ (nom_plot,) = ax1.plot(times, velocities, "black", label="Nominal")
+ (sm_plot,) = ax1.plot(times, sm_velocities, "g", label="Smooth PA", alpha=0.9)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax1.legend(handles=[nom_plot, pa_plot, sm_plot], loc='best', prop=fontP)
- ax1.set_xlabel('Time (s)')
+ fontP.set_size("x-small")
+ ax1.legend(handles=[nom_plot, pa_plot, sm_plot], loc="best", prop=fontP)
+ ax1.set_xlabel("Time (s)")
ax1.grid(True)
fig.tight_layout()
return fig
+
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
- matplotlib.rcParams.update({'figure.autolayout': True})
- matplotlib.use('Agg')
+ matplotlib.rcParams.update({"figure.autolayout": True})
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def main():
# Parse command-line arguments
usage = "%prog [options]"
opts = optparse.OptionParser(usage)
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
options, args = opts.parse_args()
if len(args) != 0:
opts.error("Incorrect number of arguments")
@@ -189,5 +221,6 @@ def main():
fig.set_size_inches(6, 2.5)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/graph_mesh.py b/scripts/graph_mesh.py
index 3a331e5d..a62f9df2 100755
--- a/scripts/graph_mesh.py
+++ b/scripts/graph_mesh.py
@@ -20,14 +20,14 @@ import matplotlib.cm as cm
import matplotlib.pyplot as plt
import matplotlib.animation as ani
-MESH_DUMP_REQUEST = json.dumps(
- {"id": 1, "method": "bed_mesh/dump_mesh"}
-)
+MESH_DUMP_REQUEST = json.dumps({"id": 1, "method": "bed_mesh/dump_mesh"})
+
def sock_error_exit(msg):
sys.stderr.write(msg + "\n")
sys.exit(-1)
+
def webhook_socket_create(uds_filename):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
while 1:
@@ -45,6 +45,7 @@ def webhook_socket_create(uds_filename):
print("Connected")
return sock
+
def process_message(msg):
try:
resp = json.loads(msg)
@@ -54,16 +55,14 @@ def process_message(msg):
return None
if "error" in resp:
err = resp["error"].get("message", "Unknown")
- sock_error_exit(
- "Error requesting mesh dump: %s" % (err,)
- )
+ sock_error_exit("Error requesting mesh dump: %s" % (err,))
return resp["result"]
def request_from_unixsocket(unix_sock_name):
print("Connecting to Unix Socket File '%s'" % (unix_sock_name,))
whsock = webhook_socket_create(unix_sock_name)
- whsock.settimeout(1.)
+ whsock.settimeout(1.0)
# send mesh query
whsock.send(MESH_DUMP_REQUEST.encode() + b"\x03")
sock_data = b""
@@ -84,11 +83,12 @@ def request_from_unixsocket(unix_sock_name):
result = process_message(msg)
if result is not None:
return result
- time.sleep(.1)
+ time.sleep(0.1)
finally:
whsock.close()
sock_error_exit("Mesh dump request timed out")
+
def request_from_websocket(url):
print("Connecting to websocket url '%s'" % (url,))
try:
@@ -101,15 +101,16 @@ def request_from_websocket(url):
end_time = time.monotonic() + 20.0
while time.monotonic() < end_time:
try:
- msg = websocket.recv(10.)
+ msg = websocket.recv(10.0)
except TimeoutError:
continue
result = process_message(msg)
if result is not None:
return result
- time.sleep(.1)
+ time.sleep(0.1)
sock_error_exit("Mesh dump request timed out")
+
def request_mesh_data(input_name):
url_match = re.match(r"((?:https?)|(?:wss?))://(.+)", input_name.lower())
if url_match is None:
@@ -129,16 +130,21 @@ def request_mesh_data(input_name):
url = "%s://%s/klippysocket" % (scheme, host)
return request_from_websocket(url)
+
class PathAnimation:
instance = None
+
def __init__(self, artist, x_travel, y_travel):
self.travel_artist = artist
self.x_travel = x_travel
self.y_travel = y_travel
fig = plt.gcf()
self.animation = ani.FuncAnimation(
- fig=fig, func=self.update, frames=self.gen_path_position(),
- cache_frame_data=False, interval=60
+ fig=fig,
+ func=self.update,
+ frames=self.gen_path_position(),
+ cache_frame_data=False,
+ interval=60,
)
PathAnimation.instance = self
@@ -164,6 +170,7 @@ def _gen_mesh_coords(min_c, max_c, count):
dist = (max_c - min_c) / (count - 1)
return [min_c + i * dist for i in range(count)]
+
def _plot_path(travel_path, probed, diff, cmd_args):
x_travel, y_travel = np.array(travel_path).transpose()
x_probed, y_probed = np.array(probed).transpose()
@@ -183,6 +190,7 @@ def _plot_path(travel_path, probed, diff, cmd_args):
if cmd_args.animate and cmd_args.output is None:
PathAnimation(travel_line, x_travel, y_travel)
+
def _format_mesh_data(matrix, params):
min_pt = (params["min_x"], params["min_y"])
max_pt = (params["max_x"], params["max_y"])
@@ -192,6 +200,7 @@ def _format_mesh_data(matrix, params):
z = np.array(matrix)
return x, y, z
+
def _set_xy_limits(mesh_data, cmd_args):
if not cmd_args.scale_plot:
return
@@ -201,12 +210,14 @@ def _set_xy_limits(mesh_data, cmd_args):
ax.set_xlim((axis_min[0], axis_max[0]))
ax.set_ylim((axis_min[1], axis_max[1]))
+
def _plot_mesh(ax, matrix, params, cmap=cm.viridis, label=None):
x, y, z = _format_mesh_data(matrix, params)
surface = ax.plot_surface(x, y, z, cmap=cmap, label=label)
scale = max(abs(z.min()), abs(z.max())) * 3
return surface, scale
+
def plot_probe_points(mesh_data, cmd_args):
"""Plot original generated points"""
calibration = mesh_data["calibration"]
@@ -217,6 +228,7 @@ def plot_probe_points(mesh_data, cmd_args):
plt.plot(x, y, "b.")
_set_xy_limits(mesh_data, cmd_args)
+
def plot_probe_path(mesh_data, cmd_args):
"""Plot probe travel path"""
calibration = mesh_data["calibration"]
@@ -227,6 +239,7 @@ def plot_probe_path(mesh_data, cmd_args):
_plot_path(path_pts, path_pts[1:-1], diff, cmd_args)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_rapid_path(mesh_data, cmd_args):
"""Plot rapid scan travel path"""
calibration = mesh_data["calibration"]
@@ -239,6 +252,7 @@ def plot_rapid_path(mesh_data, cmd_args):
_plot_path(rapid_path, probed, diff, cmd_args)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_probed_matrix(mesh_data, cmd_args):
"""Plot probed Z values"""
ax = plt.subplot(projection="3d")
@@ -259,9 +273,10 @@ def plot_probed_matrix(mesh_data, cmd_args):
surface, scale = _plot_mesh(ax, matrix, params)
ax.set_title("Probed Mesh (%s)" % (name,))
ax.set(zlim=(-scale, scale))
- plt.gcf().colorbar(surface, shrink=.75)
+ plt.gcf().colorbar(surface, shrink=0.75)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_mesh_matrix(mesh_data, cmd_args):
"""Plot mesh Z values"""
ax = plt.subplot(projection="3d")
@@ -274,9 +289,10 @@ def plot_mesh_matrix(mesh_data, cmd_args):
name = req_mesh["name"]
ax.set_title("Interpolated Mesh (%s)" % (name,))
ax.set(zlim=(-scale, scale))
- plt.gcf().colorbar(surface, shrink=.75)
+ plt.gcf().colorbar(surface, shrink=0.75)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_overlay(mesh_data, cmd_args):
"""Plots the current probed mesh overlaid with a profile"""
ax = plt.subplot(projection="3d")
@@ -301,10 +317,11 @@ def plot_overlay(mesh_data, cmd_args):
ax.set_title("Probed Mesh Overlay")
scale = max(cur_scale, prof_scale)
ax.set(zlim=(-scale, scale))
- ax.legend(loc='best')
- plt.gcf().colorbar(prof_surf, shrink=.75)
+ ax.legend(loc="best")
+ plt.gcf().colorbar(prof_surf, shrink=0.75)
_set_xy_limits(mesh_data, cmd_args)
+
def plot_delta(mesh_data, cmd_args):
"""Plots the delta between current probed mesh and a profile"""
ax = plt.subplot(projection="3d")
@@ -327,9 +344,7 @@ def plot_delta(mesh_data, cmd_args):
pfields = ("x_count", "y_count", "min_x", "max_x", "min_y", "max_y")
for field in pfields:
if abs(prof_params[field] - cur_params[field]) >= 1e-6:
- raise Exception(
- "Values for field %s do not match, cant plot deviation"
- )
+ raise Exception("Values for field %s do not match, cant plot deviation")
delta = np.array(cur_matrix) - np.array(prof_matix)
surface, scale = _plot_mesh(ax, delta, cur_params)
ax.set(zlim=(-scale, scale))
@@ -347,12 +362,12 @@ PLOT_TYPES = {
"delta": plot_delta,
}
+
def print_types(cmd_args):
- typelist = [
- "%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items()
- ]
+ typelist = ["%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items()]
print("\n".join(typelist))
+
def plot_mesh_data(cmd_args):
mesh_data = request_mesh_data(cmd_args.input)
if cmd_args.output is not None:
@@ -368,6 +383,7 @@ def plot_mesh_data(cmd_args):
else:
fig.savefig(cmd_args.output)
+
def _check_path_unique(name, path):
path = np.array(path)
unique_pts, counts = np.unique(path, return_counts=True, axis=0)
@@ -380,6 +396,7 @@ def _check_path_unique(name, path):
% (name, coord)
)
+
def _analyze_mesh(name, mesh_axes):
print("\nAnalyzing Probed Mesh %s..." % (name,))
x, y, z = mesh_axes
@@ -389,8 +406,8 @@ def _analyze_mesh(name, mesh_axes):
print(
" Min Coord (%.2f, %.2f), Max Coord (%.2f, %.2f), "
- "Probe Count: (%d, %d)" %
- (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0]))
+ "Probe Count: (%d, %d)"
+ % (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0]))
)
print(
" Mesh range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)"
@@ -398,6 +415,7 @@ def _analyze_mesh(name, mesh_axes):
)
print(" Mean: %.4f, Standard Deviation: %.4f" % (z.mean(), z.std()))
+
def _compare_mesh(name_a, name_b, mesh_a, mesh_b):
ax, ay, az = mesh_a
bx, by, bz = mesh_b
@@ -414,10 +432,21 @@ def _compare_mesh(name_a, name_b, mesh_a, mesh_b):
" Range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)\n"
" Mean: %.6f, Standard Deviation: %.6f\n"
" Absolute Max: %.6f, Absolute Mean: %.6f"
- % (delta.min(), min_x, min_y, delta.max(), max_x, max_y,
- delta.mean(), delta.std(), abs_max, abs_mean)
+ % (
+ delta.min(),
+ min_x,
+ min_y,
+ delta.max(),
+ max_x,
+ max_y,
+ delta.mean(),
+ delta.std(),
+ abs_max,
+ abs_mean,
+ )
)
+
def analyze(cmd_args):
mesh_data = request_mesh_data(cmd_args.input)
print("Analyzing Travel Path...")
@@ -461,6 +490,7 @@ def analyze(cmd_args):
for prof_name, prof_axes in formatted_data.items():
_compare_mesh(name, prof_name, current_axes, prof_axes)
+
def dump_request(cmd_args):
mesh_data = request_mesh_data(cmd_args.input)
outfile = cmd_args.output
@@ -472,57 +502,60 @@ def dump_request(cmd_args):
with open(outfile, "w") as f:
f.write(json.dumps(mesh_data))
+
def main():
parser = argparse.ArgumentParser(description="Graph Bed Mesh Data")
sub_parsers = parser.add_subparsers()
- list_parser = sub_parsers.add_parser(
- "list", help="List available plot types"
- )
+ list_parser = sub_parsers.add_parser("list", help="List available plot types")
list_parser.set_defaults(func=print_types)
plot_parser = sub_parsers.add_parser("plot", help="Plot a specified type")
analyze_parser = sub_parsers.add_parser(
"analyze", help="Perform analysis on mesh data"
)
- dump_parser = sub_parsers.add_parser(
- "dump", help="Dump API response to json file"
- )
+ dump_parser = sub_parsers.add_parser("dump", help="Dump API response to json file")
plot_parser.add_argument(
- "-a", "--animate", action="store_true",
- help="Animate paths in live preview"
+ "-a", "--animate", action="store_true", help="Animate paths in live preview"
)
plot_parser.add_argument(
- "-s", "--scale-plot", action="store_true",
- help="Use axis limits reported by Klipper to scale plot X/Y"
+ "-s",
+ "--scale-plot",
+ action="store_true",
+ help="Use axis limits reported by Klipper to scale plot X/Y",
)
plot_parser.add_argument(
- "-p", "--profile-name", type=str, default=None,
- help="Optional name of a profile to plot for 'probedz'"
+ "-p",
+ "--profile-name",
+ type=str,
+ default=None,
+ help="Optional name of a profile to plot for 'probedz'",
)
plot_parser.add_argument(
- "-o", "--output", type=str, default=None,
- help="Output file path"
+ "-o", "--output", type=str, default=None, help="Output file path"
)
plot_parser.add_argument(
- "type", metavar="<plot type>", type=str, choices=PLOT_TYPES.keys(),
- help="Type of data to graph"
+ "type",
+ metavar="<plot type>",
+ type=str,
+ choices=PLOT_TYPES.keys(),
+ help="Type of data to graph",
)
plot_parser.add_argument(
- "input", metavar="<input>",
- help="Path/url to Klipper Socket or path to json file"
+ "input",
+ metavar="<input>",
+ help="Path/url to Klipper Socket or path to json file",
)
plot_parser.set_defaults(func=plot_mesh_data)
analyze_parser.add_argument(
- "input", metavar="<input>",
- help="Path/url to Klipper Socket or path to json file"
+ "input",
+ metavar="<input>",
+ help="Path/url to Klipper Socket or path to json file",
)
analyze_parser.set_defaults(func=analyze)
dump_parser.add_argument(
- "-o", "--output", type=str, default=None,
- help="Json output file path"
+ "-o", "--output", type=str, default=None, help="Json output file path"
)
dump_parser.add_argument(
- "input", metavar="<input>",
- help="Path or url to Klipper Socket"
+ "input", metavar="<input>", help="Path or url to Klipper Socket"
)
dump_parser.set_defaults(func=dump_request)
cmd_args = parser.parse_args()
diff --git a/scripts/graph_motion.py b/scripts/graph_motion.py
index 0520343f..83072680 100755
--- a/scripts/graph_motion.py
+++ b/scripts/graph_motion.py
@@ -8,14 +8,14 @@
import optparse, datetime, math
import matplotlib
-SEG_TIME = .000100
-INV_SEG_TIME = 1. / SEG_TIME
+SEG_TIME = 0.000100
+INV_SEG_TIME = 1.0 / SEG_TIME
-SPRING_FREQ=35.0
-DAMPING_RATIO=0.05
+SPRING_FREQ = 35.0
+DAMPING_RATIO = 0.05
-CONFIG_FREQ=40.0
-CONFIG_DAMPING_RATIO=0.1
+CONFIG_FREQ = 40.0
+CONFIG_DAMPING_RATIO = 0.1
######################################################################
# Basic trapezoid motion
@@ -23,60 +23,72 @@ CONFIG_DAMPING_RATIO=0.1
# List of moves: [(start_v, end_v, move_t), ...]
Moves = [
- (0., 0., .100),
- (6.869, 89.443, None), (89.443, 89.443, .120), (89.443, 17.361, None),
- (19.410, 120., None), (120., 120., .130), (120., 5., None),
- (0., 0., 0.01),
- (-5., -100., None), (-100., -100., .100), (-100., -.5, None),
- (0., 0., .200)
+ (0.0, 0.0, 0.100),
+ (6.869, 89.443, None),
+ (89.443, 89.443, 0.120),
+ (89.443, 17.361, None),
+ (19.410, 120.0, None),
+ (120.0, 120.0, 0.130),
+ (120.0, 5.0, None),
+ (0.0, 0.0, 0.01),
+ (-5.0, -100.0, None),
+ (-100.0, -100.0, 0.100),
+ (-100.0, -0.5, None),
+ (0.0, 0.0, 0.200),
]
-ACCEL = 3000.
+ACCEL = 3000.0
MAX_JERK = ACCEL * 0.6 * SPRING_FREQ
+
def get_accel(start_v, end_v):
return ACCEL
+
def get_accel_jerk_limit(start_v, end_v):
- effective_accel = math.sqrt(MAX_JERK * abs(end_v - start_v) / 6.)
+ effective_accel = math.sqrt(MAX_JERK * abs(end_v - start_v) / 6.0)
return min(effective_accel, ACCEL)
+
# Standard constant acceleration generator
def get_acc_pos_ao2(rel_t, start_v, accel, move_t):
return (start_v + 0.5 * accel * rel_t) * rel_t
+
# Bezier curve "accel_order=4" generator
def get_acc_pos_ao4(rel_t, start_v, accel, move_t):
- inv_accel_t = 1. / move_t
+ inv_accel_t = 1.0 / move_t
accel_div_accel_t = accel * inv_accel_t
accel_div_accel_t2 = accel_div_accel_t * inv_accel_t
- c4 = -.5 * accel_div_accel_t2;
- c3 = accel_div_accel_t;
+ c4 = -0.5 * accel_div_accel_t2
+ c3 = accel_div_accel_t
c1 = start_v
return ((c4 * rel_t + c3) * rel_t * rel_t + c1) * rel_t
+
# Bezier curve "accel_order=6" generator
def get_acc_pos_ao6(rel_t, start_v, accel, move_t):
- inv_accel_t = 1. / move_t
+ inv_accel_t = 1.0 / move_t
accel_div_accel_t = accel * inv_accel_t
accel_div_accel_t2 = accel_div_accel_t * inv_accel_t
accel_div_accel_t3 = accel_div_accel_t2 * inv_accel_t
accel_div_accel_t4 = accel_div_accel_t3 * inv_accel_t
- c6 = accel_div_accel_t4;
- c5 = -3. * accel_div_accel_t3;
- c4 = 2.5 * accel_div_accel_t2;
- c1 = start_v;
- return (((c6 * rel_t + c5) * rel_t + c4)
- * rel_t * rel_t * rel_t + c1) * rel_t
+ c6 = accel_div_accel_t4
+ c5 = -3.0 * accel_div_accel_t3
+ c4 = 2.5 * accel_div_accel_t2
+ c1 = start_v
+ return (((c6 * rel_t + c5) * rel_t + c4) * rel_t * rel_t * rel_t + c1) * rel_t
+
get_acc_pos = get_acc_pos_ao2
get_acc = get_accel
+
# Calculate positions based on 'Moves' list
def gen_positions():
out = []
- start_d = start_t = t = 0.
+ start_d = start_t = t = 0.0
for start_v, end_v, move_t in Moves:
if move_t is None:
move_t = abs(end_v - start_v) / get_acc(start_v, end_v)
@@ -95,10 +107,11 @@ def gen_positions():
# Estimated motion with belt as spring
######################################################################
+
def estimate_spring(positions):
- ang_freq2 = (SPRING_FREQ * 2. * math.pi)**2
- damping_factor = 4. * math.pi * DAMPING_RATIO * SPRING_FREQ
- head_pos = head_v = 0.
+ ang_freq2 = (SPRING_FREQ * 2.0 * math.pi) ** 2
+ damping_factor = 4.0 * math.pi * DAMPING_RATIO * SPRING_FREQ
+ head_pos = head_v = 0.0
out = []
for stepper_pos in positions:
head_pos += head_v * SEG_TIME
@@ -115,15 +128,18 @@ def estimate_spring(positions):
MARGIN_TIME = 0.050
+
def time_to_index(t):
- return int(t * INV_SEG_TIME + .5)
+ return int(t * INV_SEG_TIME + 0.5)
+
def indexes(positions):
drop = time_to_index(MARGIN_TIME)
- return range(drop, len(positions)-drop)
+ return range(drop, len(positions) - drop)
+
def trim_lists(*lists):
- keep = len(lists[0]) - time_to_index(2. * MARGIN_TIME)
+ keep = len(lists[0]) - time_to_index(2.0 * MARGIN_TIME)
for l in lists:
del l[keep:]
@@ -132,70 +148,84 @@ def trim_lists(*lists):
# Common data filters
######################################################################
+
# Generate estimated first order derivative
def gen_deriv(data):
- return [0.] + [(data[i+1] - data[i]) * INV_SEG_TIME
- for i in range(len(data)-1)]
+ return [0.0] + [
+ (data[i + 1] - data[i]) * INV_SEG_TIME for i in range(len(data) - 1)
+ ]
+
# Simple average between two points smooth_time away
def calc_average(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- out[i] = .5 * (positions[i-offset] + positions[i+offset])
+ out[i] = 0.5 * (positions[i - offset] + positions[i + offset])
return out
+
# Average (via integration) of smooth_time range
def calc_smooth(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- weight = 1. / (2*offset - 1)
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ weight = 1.0 / (2 * offset - 1)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- out[i] = sum(positions[i-offset+1:i+offset]) * weight
+ out[i] = sum(positions[i - offset + 1 : i + offset]) * weight
return out
+
# Time weighted average (via integration) of smooth_time range
def calc_weighted(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- weight = 1. / offset**2
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ weight = 1.0 / offset**2
+ out = [0.0] * len(positions)
for i in indexes(positions):
- weighted_data = [positions[j] * (offset - abs(j-i))
- for j in range(i-offset, i+offset)]
+ weighted_data = [
+ positions[j] * (offset - abs(j - i)) for j in range(i - offset, i + offset)
+ ]
out[i] = sum(weighted_data) * weight
return out
+
# Weighted average (`h**2 - (t-T)**2`) of smooth_time range
def calc_weighted2(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- weight = .75 / offset**3
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ weight = 0.75 / offset**3
+ out = [0.0] * len(positions)
for i in indexes(positions):
- weighted_data = [positions[j] * (offset**2 - (j-i)**2)
- for j in range(i-offset, i+offset)]
+ weighted_data = [
+ positions[j] * (offset**2 - (j - i) ** 2)
+ for j in range(i - offset, i + offset)
+ ]
out[i] = sum(weighted_data) * weight
return out
+
# Weighted average (`(h**2 - (t-T)**2)**2`) of smooth_time range
def calc_weighted4(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- weight = 15 / (16. * offset**5)
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ weight = 15 / (16.0 * offset**5)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- weighted_data = [positions[j] * ((offset**2 - (j-i)**2))**2
- for j in range(i-offset, i+offset)]
+ weighted_data = [
+ positions[j] * ((offset**2 - (j - i) ** 2)) ** 2
+ for j in range(i - offset, i + offset)
+ ]
out[i] = sum(weighted_data) * weight
return out
+
# Weighted average (`(h - abs(t-T))**2 * (2 * abs(t-T) + h)`) of range
def calc_weighted3(positions, smooth_time):
- offset = time_to_index(smooth_time * .5)
- weight = 1. / offset**4
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.5)
+ weight = 1.0 / offset**4
+ out = [0.0] * len(positions)
for i in indexes(positions):
- weighted_data = [positions[j] * (offset - abs(j-i))**2
- * (2. * abs(j-i) + offset)
- for j in range(i-offset, i+offset)]
+ weighted_data = [
+ positions[j] * (offset - abs(j - i)) ** 2 * (2.0 * abs(j - i) + offset)
+ for j in range(i - offset, i + offset)
+ ]
out[i] = sum(weighted_data) * weight
return out
@@ -204,107 +234,119 @@ def calc_weighted3(positions, smooth_time):
# Spring motion estimation
######################################################################
+
def calc_spring_raw(positions):
- sa = (INV_SEG_TIME / (CONFIG_FREQ * 2. * math.pi))**2
- ra = 2. * CONFIG_DAMPING_RATIO * math.sqrt(sa)
- out = [0.] * len(positions)
+ sa = (INV_SEG_TIME / (CONFIG_FREQ * 2.0 * math.pi)) ** 2
+ ra = 2.0 * CONFIG_DAMPING_RATIO * math.sqrt(sa)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- out[i] = (positions[i]
- + sa * (positions[i-1] - 2.*positions[i] + positions[i+1])
- + ra * (positions[i+1] - positions[i]))
+ out[i] = (
+ positions[i]
+ + sa * (positions[i - 1] - 2.0 * positions[i] + positions[i + 1])
+ + ra * (positions[i + 1] - positions[i])
+ )
return out
+
def calc_spring_double_weighted(positions, smooth_time):
- offset = time_to_index(smooth_time * .25)
- sa = (INV_SEG_TIME / (offset * CONFIG_FREQ * 2. * math.pi))**2
- ra = 2. * CONFIG_DAMPING_RATIO * math.sqrt(sa)
- out = [0.] * len(positions)
+ offset = time_to_index(smooth_time * 0.25)
+ sa = (INV_SEG_TIME / (offset * CONFIG_FREQ * 2.0 * math.pi)) ** 2
+ ra = 2.0 * CONFIG_DAMPING_RATIO * math.sqrt(sa)
+ out = [0.0] * len(positions)
for i in indexes(positions):
- out[i] = (positions[i]
- + sa * (positions[i-offset] - 2.*positions[i]
- + positions[i+offset])
- + ra * (positions[i+1] - positions[i]))
- return calc_weighted(out, smooth_time=.5 * smooth_time)
+ out[i] = (
+ positions[i]
+ + sa * (positions[i - offset] - 2.0 * positions[i] + positions[i + offset])
+ + ra * (positions[i + 1] - positions[i])
+ )
+ return calc_weighted(out, smooth_time=0.5 * smooth_time)
+
######################################################################
# Input shapers
######################################################################
+
def get_zv_shaper():
- df = math.sqrt(1. - CONFIG_DAMPING_RATIO**2)
+ df = math.sqrt(1.0 - CONFIG_DAMPING_RATIO**2)
K = math.exp(-CONFIG_DAMPING_RATIO * math.pi / df)
- t_d = 1. / (CONFIG_FREQ * df)
- A = [1., K]
- T = [0., .5*t_d]
+ t_d = 1.0 / (CONFIG_FREQ * df)
+ A = [1.0, K]
+ T = [0.0, 0.5 * t_d]
return (A, T, "ZV")
+
def get_zvd_shaper():
- df = math.sqrt(1. - CONFIG_DAMPING_RATIO**2)
+ df = math.sqrt(1.0 - CONFIG_DAMPING_RATIO**2)
K = math.exp(-CONFIG_DAMPING_RATIO * math.pi / df)
- t_d = 1. / (CONFIG_FREQ * df)
- A = [1., 2.*K, K**2]
- T = [0., .5*t_d, t_d]
+ t_d = 1.0 / (CONFIG_FREQ * df)
+ A = [1.0, 2.0 * K, K**2]
+ T = [0.0, 0.5 * t_d, t_d]
return (A, T, "ZVD")
+
def get_mzv_shaper():
- df = math.sqrt(1. - CONFIG_DAMPING_RATIO**2)
- K = math.exp(-.75 * CONFIG_DAMPING_RATIO * math.pi / df)
- t_d = 1. / (CONFIG_FREQ * df)
+ df = math.sqrt(1.0 - CONFIG_DAMPING_RATIO**2)
+ K = math.exp(-0.75 * CONFIG_DAMPING_RATIO * math.pi / df)
+ t_d = 1.0 / (CONFIG_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, "MZV")
+
def get_ei_shaper():
- v_tol = 0.05 # vibration tolerance
- df = math.sqrt(1. - CONFIG_DAMPING_RATIO**2)
+ v_tol = 0.05 # vibration tolerance
+ df = math.sqrt(1.0 - CONFIG_DAMPING_RATIO**2)
K = math.exp(-CONFIG_DAMPING_RATIO * math.pi / df)
- t_d = 1. / (CONFIG_FREQ * df)
+ t_d = 1.0 / (CONFIG_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, "EI")
+
def get_2hump_ei_shaper():
- v_tol = 0.05 # vibration tolerance
- df = math.sqrt(1. - CONFIG_DAMPING_RATIO**2)
+ v_tol = 0.05 # vibration tolerance
+ df = math.sqrt(1.0 - CONFIG_DAMPING_RATIO**2)
K = math.exp(-CONFIG_DAMPING_RATIO * math.pi / df)
- t_d = 1. / (CONFIG_FREQ * df)
+ t_d = 1.0 / (CONFIG_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, "2-hump EI")
+
def get_3hump_ei_shaper():
- v_tol = 0.05 # vibration tolerance
- df = math.sqrt(1. - CONFIG_DAMPING_RATIO**2)
+ v_tol = 0.05 # vibration tolerance
+ df = math.sqrt(1.0 - CONFIG_DAMPING_RATIO**2)
K = math.exp(-CONFIG_DAMPING_RATIO * math.pi / df)
- t_d = 1. / (CONFIG_FREQ * df)
+ t_d = 1.0 / (CONFIG_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, "3-hump EI")
@@ -315,24 +357,27 @@ def shift_pulses(shaper):
for i in range(n):
T[i] -= ts
+
def calc_shaper(shaper, positions):
shift_pulses(shaper)
A = shaper[0]
- inv_D = 1. / sum(A)
+ inv_D = 1.0 / sum(A)
n = len(A)
T = [time_to_index(-shaper[1][j]) for j in range(n)]
- out = [0.] * len(positions)
+ out = [0.0] * len(positions)
for i in indexes(positions):
out[i] = sum([positions[i + T[j]] * A[j] for j in range(n)]) * inv_D
return out
+
# Ideal values
-SMOOTH_TIME = (2./3.) / CONFIG_FREQ
+SMOOTH_TIME = (2.0 / 3.0) / CONFIG_FREQ
+
def gen_updated_position(positions):
- #return calc_weighted(positions, 0.040)
- #return calc_spring_double_weighted(positions, SMOOTH_TIME)
- #return calc_weighted4(calc_spring_raw(positions), SMOOTH_TIME)
+ # return calc_weighted(positions, 0.040)
+ # return calc_spring_double_weighted(positions, SMOOTH_TIME)
+ # return calc_weighted4(calc_spring_raw(positions), SMOOTH_TIME)
return calc_shaper(get_ei_shaper(), positions)
@@ -340,6 +385,7 @@ def gen_updated_position(positions):
# Plotting and startup
######################################################################
+
def plot_motion():
# Nominal motion
positions = gen_positions()
@@ -352,62 +398,80 @@ def plot_motion():
# Estimated position with model of belt as spring
spring_orig = estimate_spring(positions)
spring_upd = estimate_spring(upd_positions)
- spring_diff_orig = [n-o for n, o in zip(spring_orig, positions)]
- spring_diff_upd = [n-o for n, o in zip(spring_upd, positions)]
+ spring_diff_orig = [n - o for n, o in zip(spring_orig, positions)]
+ spring_diff_upd = [n - o for n, o in zip(spring_upd, positions)]
head_velocities = gen_deriv(spring_orig)
head_accels = gen_deriv(head_velocities)
head_upd_velocities = gen_deriv(spring_upd)
head_upd_accels = gen_deriv(head_upd_velocities)
# Build plot
times = [SEG_TIME * i for i in range(len(positions))]
- trim_lists(times, velocities, accels,
- upd_velocities, upd_velocities, upd_accels,
- spring_diff_orig, spring_diff_upd,
- head_velocities, head_upd_velocities,
- head_accels, head_upd_accels)
+ trim_lists(
+ times,
+ velocities,
+ accels,
+ upd_velocities,
+ upd_velocities,
+ upd_accels,
+ spring_diff_orig,
+ spring_diff_upd,
+ head_velocities,
+ head_upd_velocities,
+ head_accels,
+ head_upd_accels,
+ )
fig, (ax1, ax2, ax3) = matplotlib.pyplot.subplots(nrows=3, sharex=True)
- ax1.set_title("Simulation: resonance freq=%.1f Hz, damping_ratio=%.3f,\n"
- "configured freq=%.1f Hz, damping_ratio = %.3f"
- % (SPRING_FREQ, DAMPING_RATIO, CONFIG_FREQ
- , CONFIG_DAMPING_RATIO))
- ax1.set_ylabel('Velocity (mm/s)')
- ax1.plot(times, upd_velocities, 'r', label='New Velocity', alpha=0.8)
- ax1.plot(times, velocities, 'g', label='Nominal Velocity', alpha=0.8)
- ax1.plot(times, head_velocities, label='Head Velocity', alpha=0.4)
- ax1.plot(times, head_upd_velocities, label='New Head Velocity', alpha=0.4)
+ ax1.set_title(
+ "Simulation: resonance freq=%.1f Hz, damping_ratio=%.3f,\n"
+ "configured freq=%.1f Hz, damping_ratio = %.3f"
+ % (SPRING_FREQ, DAMPING_RATIO, CONFIG_FREQ, CONFIG_DAMPING_RATIO)
+ )
+ ax1.set_ylabel("Velocity (mm/s)")
+ ax1.plot(times, upd_velocities, "r", label="New Velocity", alpha=0.8)
+ ax1.plot(times, velocities, "g", label="Nominal Velocity", alpha=0.8)
+ ax1.plot(times, head_velocities, label="Head Velocity", alpha=0.4)
+ ax1.plot(times, head_upd_velocities, label="New Head Velocity", alpha=0.4)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax1.legend(loc='best', prop=fontP)
+ fontP.set_size("x-small")
+ ax1.legend(loc="best", prop=fontP)
ax1.grid(True)
- ax2.set_ylabel('Acceleration (mm/s^2)')
- ax2.plot(times, upd_accels, 'r', label='New Accel', alpha=0.8)
- ax2.plot(times, accels, 'g', label='Nominal Accel', alpha=0.8)
+ ax2.set_ylabel("Acceleration (mm/s^2)")
+ ax2.plot(times, upd_accels, "r", label="New Accel", alpha=0.8)
+ ax2.plot(times, accels, "g", label="Nominal Accel", alpha=0.8)
ax2.plot(times, head_accels, alpha=0.4)
ax2.plot(times, head_upd_accels, alpha=0.4)
- ax2.set_ylim([-5. * ACCEL, 5. * ACCEL])
- ax2.legend(loc='best', prop=fontP)
+ ax2.set_ylim([-5.0 * ACCEL, 5.0 * ACCEL])
+ ax2.legend(loc="best", prop=fontP)
ax2.grid(True)
- ax3.set_ylabel('Deviation (mm)')
- ax3.plot(times, spring_diff_upd, 'r', label='New', alpha=0.8)
- ax3.plot(times, spring_diff_orig, 'g', label='Nominal', alpha=0.8)
+ ax3.set_ylabel("Deviation (mm)")
+ ax3.plot(times, spring_diff_upd, "r", label="New", alpha=0.8)
+ ax3.plot(times, spring_diff_orig, "g", label="Nominal", alpha=0.8)
ax3.grid(True)
- ax3.legend(loc='best', prop=fontP)
- ax3.set_xlabel('Time (s)')
+ ax3.legend(loc="best", prop=fontP)
+ ax3.set_xlabel("Time (s)")
return fig
+
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
- matplotlib.use('Agg')
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def main():
# Parse command-line arguments
usage = "%prog [options]"
opts = optparse.OptionParser(usage)
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
options, args = opts.parse_args()
if len(args) != 0:
opts.error("Incorrect number of arguments")
@@ -423,5 +487,6 @@ def main():
fig.set_size_inches(8, 6)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/graph_shaper.py b/scripts/graph_shaper.py
index b9a6627c..b7886e28 100755
--- a/scripts/graph_shaper.py
+++ b/scripts/graph_shaper.py
@@ -9,118 +9,125 @@ import optparse, math
import matplotlib
# A set of damping ratios to calculate shaper response for
-DAMPING_RATIOS=[0.05, 0.1, 0.2]
+DAMPING_RATIOS = [0.05, 0.1, 0.2]
# Parameters of the input shaper
-SHAPER_FREQ=50.0
-SHAPER_DAMPING_RATIO=0.1
+SHAPER_FREQ = 50.0
+SHAPER_DAMPING_RATIO = 0.1
# Simulate input shaping of step function for these true resonance frequency
# and damping ratio
-STEP_SIMULATION_RESONANCE_FREQ=60.
-STEP_SIMULATION_DAMPING_RATIO=0.15
+STEP_SIMULATION_RESONANCE_FREQ = 60.0
+STEP_SIMULATION_DAMPING_RATIO = 0.15
# If set, defines which range of frequencies to plot shaper frequency response
PLOT_FREQ_RANGE = [] # If empty, will be automatically determined
-#PLOT_FREQ_RANGE = [10., 100.]
+# PLOT_FREQ_RANGE = [10., 100.]
-PLOT_FREQ_STEP = .01
+PLOT_FREQ_STEP = 0.01
######################################################################
# Input shapers
######################################################################
+
def get_zv_shaper():
- df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
+ df = math.sqrt(1.0 - SHAPER_DAMPING_RATIO**2)
K = math.exp(-SHAPER_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, "ZV")
+
def get_zvd_shaper():
- df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
+ df = math.sqrt(1.0 - SHAPER_DAMPING_RATIO**2)
K = math.exp(-SHAPER_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, "ZVD")
+
def get_mzv_shaper():
- df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
- K = math.exp(-.75 * SHAPER_DAMPING_RATIO * math.pi / df)
- t_d = 1. / (SHAPER_FREQ * df)
+ df = math.sqrt(1.0 - SHAPER_DAMPING_RATIO**2)
+ K = math.exp(-0.75 * SHAPER_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, "MZV")
+
def get_ei_shaper():
- v_tol = 0.05 # vibration tolerance
- df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
+ v_tol = 0.05 # vibration tolerance
+ df = math.sqrt(1.0 - SHAPER_DAMPING_RATIO**2)
K = math.exp(-SHAPER_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, "EI")
+
def get_2hump_ei_shaper():
- v_tol = 0.05 # vibration tolerance
- df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
+ v_tol = 0.05 # vibration tolerance
+ df = math.sqrt(1.0 - SHAPER_DAMPING_RATIO**2)
K = math.exp(-SHAPER_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, "2-hump EI")
+
def get_3hump_ei_shaper():
- v_tol = 0.05 # vibration tolerance
- df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
+ v_tol = 0.05 # vibration tolerance
+ df = math.sqrt(1.0 - SHAPER_DAMPING_RATIO**2)
K = math.exp(-SHAPER_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, "3-hump EI")
def estimate_shaper(shaper, freq, damping_ratio):
A, T, _ = shaper
n = len(T)
- inv_D = 1. / sum(A)
- omega = 2. * math.pi * freq
+ inv_D = 1.0 / sum(A)
+ omega = 2.0 * math.pi * freq
damping = damping_ratio * omega
- omega_d = omega * math.sqrt(1. - damping_ratio**2)
+ omega_d = omega * math.sqrt(1.0 - damping_ratio**2)
S = C = 0
for i in range(n):
W = A[i] * math.exp(-damping * (T[-1] - T[i]))
S += W * math.sin(omega_d * T[i])
C += W * math.cos(omega_d * T[i])
- return math.sqrt(S*S + C*C) * inv_D
+ return math.sqrt(S * S + C * C) * inv_D
+
def shift_pulses(shaper):
A, T, name = shaper
@@ -129,6 +136,7 @@ def shift_pulses(shaper):
for i in range(n):
T[i] -= ts
+
# Shaper selection
get_shaper = get_ei_shaper
@@ -137,27 +145,31 @@ get_shaper = get_ei_shaper
# Plotting and startup
######################################################################
+
def bisect(func, left, right):
- lhs_sign = math.copysign(1., func(left))
- while right-left > 1e-8:
- mid = .5 * (left + right)
+ lhs_sign = math.copysign(1.0, func(left))
+ while right - left > 1e-8:
+ mid = 0.5 * (left + right)
val = func(mid)
- if math.copysign(1., val) == lhs_sign:
+ if math.copysign(1.0, val) == lhs_sign:
left = mid
else:
right = mid
- return .5 * (left + right)
+ return 0.5 * (left + right)
+
def find_shaper_plot_range(shaper, vib_tol):
def eval_shaper(freq):
return estimate_shaper(shaper, freq, DAMPING_RATIOS[0]) - vib_tol
+
if not PLOT_FREQ_RANGE:
- left = bisect(eval_shaper, 0., SHAPER_FREQ)
+ left = bisect(eval_shaper, 0.0, SHAPER_FREQ)
right = bisect(eval_shaper, SHAPER_FREQ, 2.4 * SHAPER_FREQ)
else:
left, right = PLOT_FREQ_RANGE
return (left, right)
+
def gen_shaper_response(shaper):
# Calculate shaper vibration response on a range of frequencies
response = []
@@ -170,39 +182,41 @@ def gen_shaper_response(shaper):
response.append(vals)
freqs.append(freq)
freq += PLOT_FREQ_STEP
- legend = ['damping ratio = %.3f' % d_r for d_r in DAMPING_RATIOS]
+ legend = ["damping ratio = %.3f" % d_r for d_r in DAMPING_RATIOS]
return freqs, response, legend
+
def gen_shaped_step_function(shaper):
# Calculate shaping of a step function
A, T, _ = shaper
- inv_D = 1. / sum(A)
+ inv_D = 1.0 / sum(A)
n = len(T)
- omega = 2. * math.pi * STEP_SIMULATION_RESONANCE_FREQ
+ omega = 2.0 * math.pi * STEP_SIMULATION_RESONANCE_FREQ
damping = STEP_SIMULATION_DAMPING_RATIO * omega
- omega_d = omega * math.sqrt(1. - STEP_SIMULATION_DAMPING_RATIO**2)
+ omega_d = omega * math.sqrt(1.0 - STEP_SIMULATION_DAMPING_RATIO**2)
phase = math.acos(STEP_SIMULATION_DAMPING_RATIO)
- t_start = T[0] - .5 / SHAPER_FREQ
+ t_start = T[0] - 0.5 / SHAPER_FREQ
t_end = T[-1] + 1.5 / STEP_SIMULATION_RESONANCE_FREQ
result = []
time = []
t = t_start
def step_response(t):
- if t < 0.:
- return 0.
- return 1. - math.exp(-damping * t) * math.sin(omega_d * t
- + phase) / math.sin(phase)
+ if t < 0.0:
+ return 0.0
+ return 1.0 - math.exp(-damping * t) * math.sin(omega_d * t + phase) / math.sin(
+ phase
+ )
while t <= t_end:
val = []
- val.append(1. if t >= 0. else 0.)
- #val.append(step_response(t))
+ val.append(1.0 if t >= 0.0 else 0.0)
+ # val.append(step_response(t))
- commanded = 0.
- response = 0.
+ commanded = 0.0
+ response = 0.0
S = C = 0
for i in range(n):
if t < T[i]:
@@ -214,8 +228,8 @@ def gen_shaped_step_function(shaper):
result.append(val)
time.append(t)
- t += .01 / SHAPER_FREQ
- legend = ['step', 'shaper commanded', 'system response']
+ t += 0.01 / SHAPER_FREQ
+ legend = ["step", "shaper commanded", "system response"]
return time, result, legend
@@ -224,46 +238,58 @@ def plot_shaper(shaper):
freqs, response, response_legend = gen_shaper_response(shaper)
time, step_vals, step_legend = gen_shaped_step_function(shaper)
- fig, (ax1, ax2) = matplotlib.pyplot.subplots(nrows=2, figsize=(10,9))
- ax1.set_title("Vibration response simulation for shaper '%s',\n"
- "shaper_freq=%.1f Hz, damping_ratio=%.3f"
- % (shaper[-1], SHAPER_FREQ, SHAPER_DAMPING_RATIO))
+ fig, (ax1, ax2) = matplotlib.pyplot.subplots(nrows=2, figsize=(10, 9))
+ ax1.set_title(
+ "Vibration response simulation for shaper '%s',\n"
+ "shaper_freq=%.1f Hz, damping_ratio=%.3f"
+ % (shaper[-1], SHAPER_FREQ, SHAPER_DAMPING_RATIO)
+ )
ax1.plot(freqs, response)
- ax1.set_ylim(bottom=0.)
+ ax1.set_ylim(bottom=0.0)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax1.legend(response_legend, loc='best', prop=fontP)
- ax1.set_xlabel('Resonance frequency, Hz')
- ax1.set_ylabel('Remaining vibrations, ratio')
+ fontP.set_size("x-small")
+ ax1.legend(response_legend, loc="best", prop=fontP)
+ ax1.set_xlabel("Resonance frequency, Hz")
+ ax1.set_ylabel("Remaining vibrations, ratio")
ax1.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
ax1.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
- ax1.grid(which='major', color='grey')
- ax1.grid(which='minor', color='lightgrey')
+ ax1.grid(which="major", color="grey")
+ ax1.grid(which="minor", color="lightgrey")
- ax2.set_title("Unit step input, resonance frequency=%.1f Hz, "
- "damping ratio=%.3f" % (STEP_SIMULATION_RESONANCE_FREQ,
- STEP_SIMULATION_DAMPING_RATIO))
+ ax2.set_title(
+ "Unit step input, resonance frequency=%.1f Hz, "
+ "damping ratio=%.3f"
+ % (STEP_SIMULATION_RESONANCE_FREQ, STEP_SIMULATION_DAMPING_RATIO)
+ )
ax2.plot(time, step_vals)
- ax2.legend(step_legend, loc='best', prop=fontP)
- ax2.set_xlabel('Time, sec')
- ax2.set_ylabel('Amplitude')
+ ax2.legend(step_legend, loc="best", prop=fontP)
+ ax2.set_xlabel("Time, sec")
+ ax2.set_ylabel("Amplitude")
ax2.grid()
fig.tight_layout()
return fig
+
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
- matplotlib.use('Agg')
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def main():
# Parse command-line arguments
usage = "%prog [options]"
opts = optparse.OptionParser(usage)
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
options, args = opts.parse_args()
if len(args) != 0:
opts.error("Incorrect number of arguments")
@@ -279,5 +305,6 @@ def main():
fig.set_size_inches(8, 6)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/graph_temp_sensor.py b/scripts/graph_temp_sensor.py
index c1d18526..fa6a680c 100755
--- a/scripts/graph_temp_sensor.py
+++ b/scripts/graph_temp_sensor.py
@@ -12,35 +12,47 @@ import matplotlib
# Dummy config / printer / etc. class emulation
######################################################################
+
class DummyConfig:
def __init__(self, config_settings):
self.config_settings = config_settings
self.sensor_factories = {}
+
# Emulate config class
def getfloat(self, option, default, **kw):
return self.config_settings.get(option, default)
+
def get(self, option, default=None):
return default
+
def get_printer(self):
return self
+
def get_name(self):
return "dummy"
+
# Emulate printer class
def load_object(self, config, name):
return self
+
def lookup_object(self, name):
return self
+
# Emulate heaters class
def add_sensor_factory(self, name, factory):
self.sensor_factories[name] = factory
+
def do_create_sensor(self, sensor_type):
return self.sensor_factories[sensor_type](self).adc_convert
+
# Emulate query_adc class
def register_adc(self, name, klass):
pass
+
# Emulate pins class
def setup_pin(self, pin_type, pin_name):
return self
+
# Emulate mcu_adc class
def setup_adc_callback(self, time, callback):
pass
@@ -50,50 +62,53 @@ class DummyConfig:
# Plotting
######################################################################
+
def plot_adc_resolution(config, sensors):
# Temperature list
all_temps = [float(i) for i in range(1, 351)]
temps = all_temps[:-1]
# Build plot
fig, (ax1, ax2) = matplotlib.pyplot.subplots(nrows=2, sharex=True)
- pullup = config.getfloat('pullup_resistor', 0.)
- adc_voltage = config.getfloat('adc_voltage', 0.)
- ax1.set_title("Temperature Sensor (pullup=%.0f, adc_voltage=%.3f)"
- % (pullup, adc_voltage))
- ax1.set_ylabel('ADC')
- ax2.set_ylabel('ADC change per 1C')
+ pullup = config.getfloat("pullup_resistor", 0.0)
+ adc_voltage = config.getfloat("adc_voltage", 0.0)
+ ax1.set_title(
+ "Temperature Sensor (pullup=%.0f, adc_voltage=%.3f)" % (pullup, adc_voltage)
+ )
+ ax1.set_ylabel("ADC")
+ ax2.set_ylabel("ADC change per 1C")
for sensor in sensors:
sc = config.do_create_sensor(sensor)
adcs = [sc.calc_adc(t) for t in all_temps]
ax1.plot(temps, adcs[:-1], label=sensor, alpha=0.6)
- adc_deltas = [abs(adcs[i+1] - adcs[i]) for i in range(len(temps))]
+ adc_deltas = [abs(adcs[i + 1] - adcs[i]) for i in range(len(temps))]
ax2.plot(temps, adc_deltas, alpha=0.6)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax1.legend(loc='best', prop=fontP)
- ax2.set_xlabel('Temperature (C)')
+ fontP.set_size("x-small")
+ ax1.legend(loc="best", prop=fontP)
+ ax2.set_xlabel("Temperature (C)")
ax1.grid(True)
ax2.grid(True)
fig.tight_layout()
return fig
+
def plot_resistance(config, sensors):
# Temperature list
all_temps = [float(i) for i in range(1, 351)]
# Build plot
fig, ax = matplotlib.pyplot.subplots()
- pullup = config.getfloat('pullup_resistor', 0.)
+ pullup = config.getfloat("pullup_resistor", 0.0)
ax.set_title("Temperature Sensor (pullup=%.0f)" % (pullup,))
- ax.set_ylabel('Resistance (Ohms)')
+ ax.set_ylabel("Resistance (Ohms)")
for sensor in sensors:
sc = config.do_create_sensor(sensor)
adcs = [sc.calc_adc(t) for t in all_temps]
rs = [pullup * adc / (1.0 - adc) for adc in adcs]
ax.plot(all_temps, rs, label=sensor, alpha=0.6)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax.legend(loc='best', prop=fontP)
- ax.set_xlabel('Temperature (C)')
+ fontP.set_size("x-small")
+ ax.legend(loc="best", prop=fontP)
+ ax.set_xlabel("Temperature (C)")
ax.grid(True)
fig.tight_layout()
return fig
@@ -103,50 +118,81 @@ def plot_resistance(config, sensors):
# Startup
######################################################################
+
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
- matplotlib.rcParams.update({'figure.autolayout': True})
- matplotlib.use('Agg')
+ matplotlib.rcParams.update({"figure.autolayout": True})
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def import_sensors(config):
global extras
# Load adc_temperature.py and thermistor.py modules
- kdir = os.path.join(os.path.dirname(__file__), '..', 'klippy')
+ kdir = os.path.join(os.path.dirname(__file__), "..", "klippy")
sys.path.append(kdir)
import extras.adc_temperature, extras.thermistor
+
extras.thermistor.load_config(config)
extras.adc_temperature.load_config(config)
+
def main():
# Parse command-line arguments
usage = "%prog [options]"
opts = optparse.OptionParser(usage)
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
- opts.add_option("-p", "--pullup", type="float", dest="pullup",
- default=4700., help="pullup resistor")
- opts.add_option("-v", "--voltage", type="float", dest="voltage",
- default=5., help="pullup resistor")
- opts.add_option("-s", "--sensors", type="string", dest="sensors",
- default="", help="list of sensors (comma separated)")
- opts.add_option("-r", "--resistance", action="store_true",
- help="graph sensor resistance")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
+ opts.add_option(
+ "-p",
+ "--pullup",
+ type="float",
+ dest="pullup",
+ default=4700.0,
+ help="pullup resistor",
+ )
+ opts.add_option(
+ "-v",
+ "--voltage",
+ type="float",
+ dest="voltage",
+ default=5.0,
+ help="pullup resistor",
+ )
+ opts.add_option(
+ "-s",
+ "--sensors",
+ type="string",
+ dest="sensors",
+ default="",
+ help="list of sensors (comma separated)",
+ )
+ opts.add_option(
+ "-r", "--resistance", action="store_true", help="graph sensor resistance"
+ )
options, args = opts.parse_args()
if len(args) != 0:
opts.error("Incorrect number of arguments")
# Import sensors
- config_settings = {'pullup_resistor': options.pullup,
- 'adc_voltage': options.voltage}
+ config_settings = {
+ "pullup_resistor": options.pullup,
+ "adc_voltage": options.voltage,
+ }
config = DummyConfig(config_settings)
import_sensors(config)
# Determine sensors to graph
if options.sensors:
- sensors = [s.strip() for s in options.sensors.split(',')]
+ sensors = [s.strip() for s in options.sensors.split(",")]
else:
sensors = sorted(config.sensor_factories.keys())
@@ -164,5 +210,6 @@ def main():
fig.set_size_inches(8, 6)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/graphstats.py b/scripts/graphstats.py
index 5cd2ad34..4a8a7164 100755
--- a/scripts/graphstats.py
+++ b/scripts/graphstats.py
@@ -7,88 +7,107 @@
import optparse, datetime
import matplotlib
-MAXBANDWIDTH=25000.
-MAXBUFFER=2.
-STATS_INTERVAL=5.
-TASK_MAX=0.0025
+MAXBANDWIDTH = 25000.0
+MAXBUFFER = 2.0
+STATS_INTERVAL = 5.0
+TASK_MAX = 0.0025
APPLY_PREFIX = [
- 'mcu_awake', 'mcu_task_avg', 'mcu_task_stddev', 'bytes_write',
- 'bytes_read', 'bytes_retransmit', 'freq', 'adj',
- 'target', 'temp', 'pwm'
+ "mcu_awake",
+ "mcu_task_avg",
+ "mcu_task_stddev",
+ "bytes_write",
+ "bytes_read",
+ "bytes_retransmit",
+ "freq",
+ "adj",
+ "target",
+ "temp",
+ "pwm",
]
+
def parse_log(logname, mcu):
if mcu is None:
mcu = "mcu"
mcu_prefix = mcu + ":"
- apply_prefix = { p: 1 for p in APPLY_PREFIX }
- f = open(logname, 'r')
+ apply_prefix = {p: 1 for p in APPLY_PREFIX}
+ f = open(logname, "r")
out = []
for line in f:
parts = line.split()
- if not parts or parts[0] not in ('Stats', 'INFO:root:Stats'):
- #if parts and parts[0] == 'INFO:root:shutdown:':
+ if not parts or parts[0] not in ("Stats", "INFO:root:Stats"):
+ # if parts and parts[0] == 'INFO:root:shutdown:':
# break
continue
prefix = ""
keyparts = {}
for p in parts[2:]:
- if '=' not in p:
+ if "=" not in p:
prefix = p
if prefix == mcu_prefix:
- prefix = ''
+ prefix = ""
continue
- name, val = p.split('=', 1)
+ name, val = p.split("=", 1)
if name in apply_prefix:
name = prefix + name
keyparts[name] = val
- if 'print_time' not in keyparts:
+ if "print_time" not in keyparts:
continue
- keyparts['#sampletime'] = float(parts[1][:-1])
+ keyparts["#sampletime"] = float(parts[1][:-1])
out.append(keyparts)
f.close()
return out
+
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
- matplotlib.use('Agg')
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def find_print_restarts(data):
runoff_samples = {}
- last_runoff_start = last_buffer_time = last_sampletime = 0.
+ last_runoff_start = last_buffer_time = last_sampletime = 0.0
last_print_stall = 0
for d in reversed(data):
# Check for buffer runoff
- sampletime = d['#sampletime']
- buffer_time = float(d.get('buffer_time', 0.))
- if (last_runoff_start and last_sampletime - sampletime < 5
- and buffer_time > last_buffer_time):
+ sampletime = d["#sampletime"]
+ buffer_time = float(d.get("buffer_time", 0.0))
+ if (
+ last_runoff_start
+ and last_sampletime - sampletime < 5
+ and buffer_time > last_buffer_time
+ ):
runoff_samples[last_runoff_start][1].append(sampletime)
- elif buffer_time < 1.:
+ elif buffer_time < 1.0:
last_runoff_start = sampletime
runoff_samples[last_runoff_start] = [False, [sampletime]]
else:
- last_runoff_start = 0.
+ last_runoff_start = 0.0
last_buffer_time = buffer_time
last_sampletime = sampletime
# Check for print stall
- print_stall = int(d['print_stall'])
+ print_stall = int(d["print_stall"])
if print_stall < last_print_stall:
if last_runoff_start:
runoff_samples[last_runoff_start][0] = True
last_print_stall = print_stall
- sample_resets = {sampletime: 1 for stall, samples in runoff_samples.values()
- for sampletime in samples if not stall}
+ sample_resets = {
+ sampletime: 1
+ for stall, samples in runoff_samples.values()
+ for sampletime in samples
+ if not stall
+ }
return sample_resets
+
def plot_mcu(data, maxbw):
# Generate data for plot
- basetime = lasttime = data[0]['#sampletime']
- lastbw = float(data[0]['bytes_write']) + float(data[0]['bytes_retransmit'])
+ basetime = lasttime = data[0]["#sampletime"]
+ lastbw = float(data[0]["bytes_write"]) + float(data[0]["bytes_retransmit"])
sample_resets = find_print_restarts(data)
times = []
bwdeltas = []
@@ -96,165 +115,171 @@ def plot_mcu(data, maxbw):
awake = []
hostbuffers = []
for d in data:
- st = d['#sampletime']
+ st = d["#sampletime"]
timedelta = st - lasttime
- if timedelta <= 0.:
+ if timedelta <= 0.0:
continue
- bw = float(d['bytes_write']) + float(d['bytes_retransmit'])
+ bw = float(d["bytes_write"]) + float(d["bytes_retransmit"])
if bw < lastbw:
lastbw = bw
continue
- load = float(d['mcu_task_avg']) + 3*float(d['mcu_task_stddev'])
- if st - basetime < 15.:
- load = 0.
- pt = float(d['print_time'])
- hb = float(d['buffer_time'])
+ load = float(d["mcu_task_avg"]) + 3 * float(d["mcu_task_stddev"])
+ if st - basetime < 15.0:
+ load = 0.0
+ pt = float(d["print_time"])
+ hb = float(d["buffer_time"])
if hb >= MAXBUFFER or st in sample_resets:
- hb = 0.
+ hb = 0.0
else:
- hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
+ hb = 100.0 * (MAXBUFFER - hb) / MAXBUFFER
hostbuffers.append(hb)
times.append(datetime.datetime.utcfromtimestamp(st))
- bwdeltas.append(100. * (bw - lastbw) / (maxbw * timedelta))
- loads.append(100. * load / TASK_MAX)
- awake.append(100. * float(d.get('mcu_awake', 0.)) / STATS_INTERVAL)
+ bwdeltas.append(100.0 * (bw - lastbw) / (maxbw * timedelta))
+ loads.append(100.0 * load / TASK_MAX)
+ awake.append(100.0 * float(d.get("mcu_awake", 0.0)) / STATS_INTERVAL)
lasttime = st
lastbw = bw
# Build plot
fig, ax1 = matplotlib.pyplot.subplots()
ax1.set_title("MCU bandwidth and load utilization")
- ax1.set_xlabel('Time')
- ax1.set_ylabel('Usage (%)')
- ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth', alpha=0.8)
- ax1.plot_date(times, loads, 'r', label='MCU load', alpha=0.8)
- ax1.plot_date(times, hostbuffers, 'c', label='Host buffer', alpha=0.8)
- ax1.plot_date(times, awake, 'y', label='Awake time', alpha=0.6)
+ ax1.set_xlabel("Time")
+ ax1.set_ylabel("Usage (%)")
+ ax1.plot_date(times, bwdeltas, "g", label="Bandwidth", alpha=0.8)
+ ax1.plot_date(times, loads, "r", label="MCU load", alpha=0.8)
+ ax1.plot_date(times, hostbuffers, "c", label="Host buffer", alpha=0.8)
+ ax1.plot_date(times, awake, "y", label="Awake time", alpha=0.6)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax1.legend(loc='best', prop=fontP)
- ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
+ fontP.set_size("x-small")
+ ax1.legend(loc="best", prop=fontP)
+ ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
ax1.grid(True)
return fig
+
def plot_system(data):
# Generate data for plot
- lasttime = data[0]['#sampletime']
- lastcputime = float(data[0]['cputime'])
+ lasttime = data[0]["#sampletime"]
+ lastcputime = float(data[0]["cputime"])
times = []
sysloads = []
cputimes = []
memavails = []
for d in data:
- st = d['#sampletime']
+ st = d["#sampletime"]
timedelta = st - lasttime
- if timedelta <= 0.:
+ if timedelta <= 0.0:
continue
lasttime = st
times.append(datetime.datetime.utcfromtimestamp(st))
- cputime = float(d['cputime'])
- cpudelta = max(0., min(1.5, (cputime - lastcputime) / timedelta))
+ cputime = float(d["cputime"])
+ cpudelta = max(0.0, min(1.5, (cputime - lastcputime) / timedelta))
lastcputime = cputime
- cputimes.append(cpudelta * 100.)
- sysloads.append(float(d['sysload']) * 100.)
- memavails.append(float(d['memavail']))
+ cputimes.append(cpudelta * 100.0)
+ sysloads.append(float(d["sysload"]) * 100.0)
+ memavails.append(float(d["memavail"]))
# Build plot
fig, ax1 = matplotlib.pyplot.subplots()
ax1.set_title("System load utilization")
- ax1.set_xlabel('Time')
- ax1.set_ylabel('Load (% of a core)')
- ax1.plot_date(times, sysloads, '-', label='system load',
- color='cyan', alpha=0.8)
- ax1.plot_date(times, cputimes, '-', label='process time',
- color='red', alpha=0.8)
+ ax1.set_xlabel("Time")
+ ax1.set_ylabel("Load (% of a core)")
+ ax1.plot_date(times, sysloads, "-", label="system load", color="cyan", alpha=0.8)
+ ax1.plot_date(times, cputimes, "-", label="process time", color="red", alpha=0.8)
ax2 = ax1.twinx()
- ax2.set_ylabel('Available memory (KB)')
- ax2.plot_date(times, memavails, '-', label='system memory',
- color='yellow', alpha=0.3)
+ ax2.set_ylabel("Available memory (KB)")
+ ax2.plot_date(
+ times, memavails, "-", label="system memory", color="yellow", alpha=0.3
+ )
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
+ fontP.set_size("x-small")
ax1li, ax1la = ax1.get_legend_handles_labels()
ax2li, ax2la = ax2.get_legend_handles_labels()
- ax1.legend(ax1li + ax2li, ax1la + ax2la, loc='best', prop=fontP)
- ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
+ ax1.legend(ax1li + ax2li, ax1la + ax2la, loc="best", prop=fontP)
+ ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
ax1.grid(True)
return fig
+
def plot_mcu_frequencies(data):
all_keys = {}
for d in data:
all_keys.update(d)
- graph_keys = { key: ([], []) for key in all_keys
- if (key in ("freq", "adj")
- or (key.endswith(":freq") or key.endswith(":adj"))) }
+ graph_keys = {
+ key: ([], [])
+ for key in all_keys
+ if (key in ("freq", "adj") or (key.endswith(":freq") or key.endswith(":adj")))
+ }
for d in data:
- st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
+ st = datetime.datetime.utcfromtimestamp(d["#sampletime"])
for key, (times, values) in graph_keys.items():
val = d.get(key)
- if val not in (None, '0', '1'):
+ if val not in (None, "0", "1"):
times.append(st)
values.append(float(val))
- est_mhz = { key: round((sum(values)/len(values)) / 1000000.)
- for key, (times, values) in graph_keys.items() }
+ est_mhz = {
+ key: round((sum(values) / len(values)) / 1000000.0)
+ for key, (times, values) in graph_keys.items()
+ }
# Build plot
fig, ax1 = matplotlib.pyplot.subplots()
ax1.set_title("MCU frequencies")
- ax1.set_xlabel('Time')
- ax1.set_ylabel('Microsecond deviation')
+ ax1.set_xlabel("Time")
+ ax1.set_ylabel("Microsecond deviation")
for key in sorted(graph_keys):
times, values = graph_keys[key]
mhz = est_mhz[key]
label = "%s(%dMhz)" % (key, mhz)
- hz = mhz * 1000000.
- ax1.plot_date(times, [(v - hz)/mhz for v in values], '.', label=label)
+ hz = mhz * 1000000.0
+ ax1.plot_date(times, [(v - hz) / mhz for v in values], ".", label=label)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax1.legend(loc='best', prop=fontP)
- ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
- ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%d'))
+ fontP.set_size("x-small")
+ ax1.legend(loc="best", prop=fontP)
+ ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
+ ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("%d"))
ax1.grid(True)
return fig
+
def plot_mcu_frequency(data, mcu):
all_keys = {}
for d in data:
all_keys.update(d)
- graph_keys = { key: ([], []) for key in all_keys
- if key in ("freq", "adj") }
+ graph_keys = {key: ([], []) for key in all_keys if key in ("freq", "adj")}
for d in data:
- st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
+ st = datetime.datetime.utcfromtimestamp(d["#sampletime"])
for key, (times, values) in graph_keys.items():
val = d.get(key)
- if val not in (None, '0', '1'):
+ if val not in (None, "0", "1"):
times.append(st)
values.append(float(val))
# Build plot
fig, ax1 = matplotlib.pyplot.subplots()
ax1.set_title("MCU '%s' frequency" % (mcu,))
- ax1.set_xlabel('Time')
- ax1.set_ylabel('Frequency')
+ ax1.set_xlabel("Time")
+ ax1.set_ylabel("Frequency")
for key in sorted(graph_keys):
times, values = graph_keys[key]
- ax1.plot_date(times, values, '.', label=key)
+ ax1.plot_date(times, values, ".", label=key)
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
- ax1.legend(loc='best', prop=fontP)
- ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
- ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%d'))
+ fontP.set_size("x-small")
+ ax1.legend(loc="best", prop=fontP)
+ ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
+ ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("%d"))
ax1.grid(True)
return fig
+
def plot_temperature(data, heaters):
fig, ax1 = matplotlib.pyplot.subplots()
ax2 = ax1.twinx()
- for heater in heaters.split(','):
+ for heater in heaters.split(","):
heater = heater.strip()
- temp_key = heater + ':' + 'temp'
- target_key = heater + ':' + 'target'
- pwm_key = heater + ':' + 'pwm'
+ temp_key = heater + ":" + "temp"
+ target_key = heater + ":" + "target"
+ pwm_key = heater + ":" + "pwm"
times = []
temps = []
targets = []
@@ -263,45 +288,64 @@ def plot_temperature(data, heaters):
temp = d.get(temp_key)
if temp is None:
continue
- times.append(datetime.datetime.utcfromtimestamp(d['#sampletime']))
+ times.append(datetime.datetime.utcfromtimestamp(d["#sampletime"]))
temps.append(float(temp))
- pwm.append(float(d.get(pwm_key, 0.)))
- targets.append(float(d.get(target_key, 0.)))
- ax1.plot_date(times, temps, '-', label='%s temp' % (heater,), alpha=0.8)
+ pwm.append(float(d.get(pwm_key, 0.0)))
+ targets.append(float(d.get(target_key, 0.0)))
+ ax1.plot_date(times, temps, "-", label="%s temp" % (heater,), alpha=0.8)
if any(targets):
- label = '%s target' % (heater,)
- ax1.plot_date(times, targets, '-', label=label, alpha=0.3)
+ label = "%s target" % (heater,)
+ ax1.plot_date(times, targets, "-", label=label, alpha=0.3)
if any(pwm):
- label = '%s pwm' % (heater,)
- ax2.plot_date(times, pwm, '-', label=label, alpha=0.2)
+ label = "%s pwm" % (heater,)
+ ax2.plot_date(times, pwm, "-", label=label, alpha=0.2)
# Build plot
ax1.set_title("Temperature of %s" % (heaters,))
- ax1.set_xlabel('Time')
- ax1.set_ylabel('Temperature')
- ax2.set_ylabel('pwm')
+ ax1.set_xlabel("Time")
+ ax1.set_ylabel("Temperature")
+ ax2.set_ylabel("pwm")
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
+ fontP.set_size("x-small")
ax1li, ax1la = ax1.get_legend_handles_labels()
ax2li, ax2la = ax2.get_legend_handles_labels()
- ax1.legend(ax1li + ax2li, ax1la + ax2la, loc='best', prop=fontP)
- ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
+ ax1.legend(ax1li + ax2li, ax1la + ax2la, loc="best", prop=fontP)
+ ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
ax1.grid(True)
return fig
+
def main():
# Parse command-line arguments
usage = "%prog [options] <logfile>"
opts = optparse.OptionParser(usage)
- opts.add_option("-f", "--frequency", action="store_true",
- help="graph mcu frequency")
- opts.add_option("-s", "--system", action="store_true",
- help="graph system load")
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
- opts.add_option("-t", "--temperature", type="string", dest="heater",
- default=None, help="graph heater temperature")
- opts.add_option("-m", "--mcu", type="string", dest="mcu", default=None,
- help="limit stats to the given mcu")
+ opts.add_option(
+ "-f", "--frequency", action="store_true", help="graph mcu frequency"
+ )
+ opts.add_option("-s", "--system", action="store_true", help="graph system load")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
+ opts.add_option(
+ "-t",
+ "--temperature",
+ type="string",
+ dest="heater",
+ default=None,
+ help="graph heater temperature",
+ )
+ opts.add_option(
+ "-m",
+ "--mcu",
+ type="string",
+ dest="mcu",
+ default=None,
+ help="limit stats to the given mcu",
+ )
options, args = opts.parse_args()
if len(args) != 1:
opts.error("Incorrect number of arguments")
@@ -333,5 +377,6 @@ def main():
fig.set_size_inches(8, 6)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/logextract.py b/scripts/logextract.py
index 592f6f8a..3050ff0b 100755
--- a/scripts/logextract.py
+++ b/scripts/logextract.py
@@ -6,6 +6,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, re, collections, ast, itertools
+
def format_comment(line_num, line):
return "# %6d: %s" % (line_num, line)
@@ -14,6 +15,7 @@ def format_comment(line_num, line):
# Config file extraction
######################################################################
+
class GatherConfig:
def __init__(self, configs, line_num, recent_lines, logname):
self.configs = configs
@@ -22,12 +24,14 @@ class GatherConfig:
self.filename = "%s.config%04d.cfg" % (logname, self.config_num)
self.config_lines = []
self.comments = []
+
def add_line(self, line_num, line):
- if line != '=======================':
+ if line != "=======================":
self.config_lines.append(line)
return True
self.finalize()
return False
+
def finalize(self):
lines = tuple(self.config_lines)
ch = self.configs.get(lines)
@@ -36,13 +40,15 @@ class GatherConfig:
else:
ch.comments.extend(self.comments)
ch.comments.append(format_comment(self.line_num, "config file"))
+
def add_comment(self, comment):
if comment is not None:
self.comments.append(comment)
+
def write_file(self):
lines = itertools.chain(self.comments, self.config_lines)
- lines = ('%s\n' % l for l in lines)
- with open(self.filename, 'wt') as f:
+ lines = ("%s\n" % l for l in lines)
+ with open(self.filename, "wt") as f:
f.writelines(lines)
@@ -52,6 +58,7 @@ class GatherConfig:
uart_r = re.compile(r"tmcuart_(?:send|response) oid=[0-9]+ (?:read|write)=")
+
class TMCUartHelper:
def _calc_crc8(self, data):
# Generate a CRC8-ATM value for a bytearray
@@ -61,33 +68,46 @@ class TMCUartHelper:
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, data):
# Extract a uart read request message
if len(data) != 5:
@@ -98,13 +118,14 @@ class TMCUartHelper:
mval |= d << pos
pos += 8
# Extract register value
- addr = (mval >> 11) & 0xff
- reg = (mval >> 21) & 0xff
+ addr = (mval >> 11) & 0xFF
+ reg = (mval >> 21) & 0xFF
# Verify start/stop bits and crc
- encoded_data = self._encode_read(0xf5, addr, reg)
+ encoded_data = self._encode_read(0xF5, addr, reg)
if data != encoded_data:
return "Invalid: %s" % (self.pretty_print(addr, reg),)
return self.pretty_print(addr, reg)
+
def _decode_reg(self, data):
# Extract a uart read response message
if len(data) != 10:
@@ -115,25 +136,31 @@ class TMCUartHelper:
mval |= d << pos
pos += 8
# Extract register value
- addr = (mval >> 11) & 0xff
- reg = (mval >> 21) & 0xff
- val = ((((mval >> 31) & 0xff) << 24) | (((mval >> 41) & 0xff) << 16)
- | (((mval >> 51) & 0xff) << 8) | ((mval >> 61) & 0xff))
- sync = 0xf5
- if addr == 0xff:
+ addr = (mval >> 11) & 0xFF
+ reg = (mval >> 21) & 0xFF
+ val = (
+ (((mval >> 31) & 0xFF) << 24)
+ | (((mval >> 41) & 0xFF) << 16)
+ | (((mval >> 51) & 0xFF) << 8)
+ | ((mval >> 61) & 0xFF)
+ )
+ sync = 0xF5
+ if addr == 0xFF:
sync = 0x05
# Verify start/stop bits and crc
encoded_data = self._encode_write(sync, addr, reg, val)
if data != encoded_data:
- #print("Got %s vs %s" % (repr(data), repr(encoded_data)))
+ # print("Got %s vs %s" % (repr(data), repr(encoded_data)))
return "Invalid:%s" % (self.pretty_print(addr, reg, val),)
return self.pretty_print(addr, reg, val)
+
def pretty_print(self, addr, reg, val=None):
if val is None:
return "(%x@%x)" % (reg, addr)
if reg & 0x80:
return "(%x@%x=%08x)" % (reg & ~0x80, addr, val)
return "(%x@%x==%08x)" % (reg, addr, val)
+
def parse_msg(self, msg):
data = bytearray(msg)
if len(data) == 10:
@@ -149,16 +176,28 @@ class TMCUartHelper:
# Shutdown extraction
######################################################################
+
def add_high_bits(val, ref, mask):
half = (mask + 1) // 2
return ref + ((val - (ref & mask) + half) & mask) - half
+
count_s = r"(?P<count>[0-9]+)"
time_s = r"(?P<time>[0-9]+[.][0-9]+)"
esttime_s = r"(?P<esttime>[0-9]+[.][0-9]+)"
shortseq_s = r"(?P<shortseq>[0-9a-f])"
-sent_r = re.compile(r"^Sent " + count_s + " " + esttime_s + " " + time_s
- + " [0-9]+: seq: 1" + shortseq_s + ",")
+sent_r = re.compile(
+ r"^Sent "
+ + count_s
+ + " "
+ + esttime_s
+ + " "
+ + time_s
+ + " [0-9]+: seq: 1"
+ + shortseq_s
+ + ","
+)
+
# MCU "Sent" shutdown message parsing
class MCUSentStream:
@@ -166,61 +205,81 @@ class MCUSentStream:
self.mcu = mcu
self.sent_stream = []
self.send_count = count
+
def parse_line(self, line_num, line):
m = sent_r.match(line)
if m is not None:
- shortseq = int(m.group('shortseq'), 16)
- seq = (self.mcu.shutdown_seq + int(m.group('count'))
- - self.send_count)
- seq = add_high_bits(shortseq, seq, 0xf)
- ts = float(m.group('time'))
- esttime = float(m.group('esttime'))
- self.mcu.sent_time_to_seq[(esttime, seq & 0xf)] = seq
+ shortseq = int(m.group("shortseq"), 16)
+ seq = self.mcu.shutdown_seq + int(m.group("count")) - self.send_count
+ seq = add_high_bits(shortseq, seq, 0xF)
+ ts = float(m.group("time"))
+ esttime = float(m.group("esttime"))
+ self.mcu.sent_time_to_seq[(esttime, seq & 0xF)] = seq
self.mcu.sent_seq_to_time[seq] = ts
line = self.mcu.annotate(line, seq, ts)
self.sent_stream.append((ts, line_num, line))
return True, None
return self.mcu.parse_line(line_num, line)
+
def get_lines(self):
return self.sent_stream
-receive_r = re.compile(r"^Receive: " + count_s + " " + time_s + " " + esttime_s
- + " [0-9]+: seq: 1" + shortseq_s + ",")
+
+receive_r = re.compile(
+ r"^Receive: "
+ + count_s
+ + " "
+ + time_s
+ + " "
+ + esttime_s
+ + " [0-9]+: seq: 1"
+ + shortseq_s
+ + ","
+)
+
# MCU "Receive" shutdown message parsing
class MCUReceiveStream:
def __init__(self, mcu):
self.mcu = mcu
self.receive_stream = []
+
def parse_line(self, line_num, line):
m = receive_r.match(line)
if m is not None:
- shortseq = int(m.group('shortseq'), 16)
- ts = float(m.group('time'))
- esttime = float(m.group('esttime'))
- seq = self.mcu.sent_time_to_seq.get((esttime, (shortseq - 1) & 0xf))
+ shortseq = int(m.group("shortseq"), 16)
+ ts = float(m.group("time"))
+ esttime = float(m.group("esttime"))
+ seq = self.mcu.sent_time_to_seq.get((esttime, (shortseq - 1) & 0xF))
if seq is not None:
self.mcu.receive_seq_to_time[seq + 1] = ts
line = self.mcu.annotate(line, seq, ts)
self.receive_stream.append((ts, line_num, line))
return True, None
return self.mcu.parse_line(line_num, line)
+
def get_lines(self):
return self.receive_stream
+
stats_seq_s = r" send_seq=(?P<sseq>[0-9]+) receive_seq=(?P<rseq>[0-9]+) "
serial_dump_r = re.compile(r"^Dumping serial stats: .*" + stats_seq_s)
send_dump_r = re.compile(r"^Dumping send queue " + count_s + " messages$")
receive_dump_r = re.compile(r"^Dumping receive queue " + count_s + " messages$")
-clock_r = re.compile(r"^clocksync state: mcu_freq=(?P<freq>[0-9]+) .*"
- + r" clock_est=\((?P<st>[^ ]+)"
- + r" (?P<sc>[0-9]+) (?P<f>[^ ]+)\)")
+clock_r = re.compile(
+ r"^clocksync state: mcu_freq=(?P<freq>[0-9]+) .*"
+ + r" clock_est=\((?P<st>[^ ]+)"
+ + r" (?P<sc>[0-9]+) (?P<f>[^ ]+)\)"
+)
repl_seq_r = re.compile(r": seq: 1" + shortseq_s)
clock_s = r"(?P<clock>[0-9]+)"
repl_clock_r = re.compile(r"clock=" + clock_s)
-repl_uart_r = re.compile(r"tmcuart_(?:response|send) oid=[0-9]+"
- + r" (?:read|write)=b?(?P<msg>(?:'[^']*'"
- + r'|"[^"]*"))')
+repl_uart_r = re.compile(
+ r"tmcuart_(?:response|send) oid=[0-9]+"
+ + r" (?:read|write)=b?(?P<msg>(?:'[^']*'"
+ + r'|"[^"]*"))'
+)
+
# MCU shutdown message parsing
class MCUStream:
@@ -230,191 +289,223 @@ class MCUStream:
self.sent_seq_to_time = {}
self.receive_seq_to_time = {}
self.mcu_freq = 1
- self.clock_est = (0., 0., 1.)
+ self.clock_est = (0.0, 0.0, 1.0)
self.shutdown_seq = None
+
def trans_clock(self, clock, ts):
sample_time, sample_clock, freq = self.clock_est
exp_clock = int(sample_clock + (ts - sample_time) * freq)
- ext_clock = add_high_bits(clock, exp_clock, 0xffffffff)
+ ext_clock = add_high_bits(clock, exp_clock, 0xFFFFFFFF)
return sample_time + (ext_clock - sample_clock) / freq
+
def annotate(self, line, seq, ts):
if seq is not None:
line = repl_seq_r.sub(r"\g<0>(%d)" % (seq,), line)
+
def clock_update(m):
return m.group(0).rstrip() + "(%.6f)" % (
- self.trans_clock(int(m.group('clock')), ts),)
+ self.trans_clock(int(m.group("clock")), ts),
+ )
+
line = repl_clock_r.sub(clock_update, line)
+
def uart_update(m):
- msg = ast.literal_eval('b' + m.group('msg'))
+ msg = ast.literal_eval("b" + m.group("msg"))
msg = TMCUartHelper().parse_msg(msg)
return m.group(0).rstrip() + msg
+
line = repl_uart_r.sub(uart_update, line)
- if self.name != 'mcu':
+ if self.name != "mcu":
line = "mcu '%s': %s" % (self.name, line)
return line
+
def parse_line(self, line_num, line):
m = clock_r.match(line)
if m is not None:
- self.mcu_freq = int(m.group('freq'))
- st = float(m.group('st'))
- sc = int(m.group('sc'))
- f = float(m.group('f'))
+ self.mcu_freq = int(m.group("freq"))
+ st = float(m.group("st"))
+ sc = int(m.group("sc"))
+ f = float(m.group("f"))
self.clock_est = (st, sc, f)
m = serial_dump_r.match(line)
if m is not None:
- self.shutdown_seq = int(m.group('rseq'))
+ self.shutdown_seq = int(m.group("rseq"))
m = send_dump_r.match(line)
if m is not None:
- return True, MCUSentStream(self, int(m.group('count')))
+ return True, MCUSentStream(self, int(m.group("count")))
m = receive_dump_r.match(line)
if m is not None:
return True, MCUReceiveStream(self)
return False, None
+
def get_lines(self):
return []
-stepper_move_r = re.compile(r"^queue_step " + count_s + r": t=" + clock_s
- + r" ")
+
+stepper_move_r = re.compile(r"^queue_step " + count_s + r": t=" + clock_s + r" ")
+
# Kinematic "trapq" shutdown message parsing
class StepperStream:
def __init__(self, name, mcu_name, mcus):
self.name = name
self.stepper_stream = []
- self.clock_est = (0., 0., 1.)
+ self.clock_est = (0.0, 0.0, 1.0)
mcu = mcus.get(mcu_name)
if mcu is not None:
self.clock_est = mcu.clock_est
+
def parse_line(self, line_num, line):
m = stepper_move_r.match(line)
if m is not None:
# Convert clock to systime
- clock = int(m.group('clock'))
+ clock = int(m.group("clock"))
sample_time, sample_clock, freq = self.clock_est
ts = sample_time + (clock - sample_clock) / freq
# Add systime to log
- parts = line.split(' ', 4)
+ parts = line.split(" ", 4)
parts[0] = "%s queue_step" % (self.name,)
- parts[2] += '(%.6f)' % (ts,)
- self.stepper_stream.append((ts, line_num, ' '.join(parts)))
+ parts[2] += "(%.6f)" % (ts,)
+ self.stepper_stream.append((ts, line_num, " ".join(parts)))
return True, None
return False, None
+
def get_lines(self):
return self.stepper_stream
+
trapq_move_r = re.compile(r"^move " + count_s + r": pt=" + time_s)
+
# Kinematic "trapq" shutdown message parsing
class TrapQStream:
def __init__(self, name, mcus):
self.name = name
self.trapq_stream = []
self.mcu_freq = 1
- self.clock_est = (0., 0., 1.)
+ self.clock_est = (0.0, 0.0, 1.0)
mcu = mcus.get("mcu")
if mcu is not None:
self.mcu_freq = mcu.mcu_freq
self.clock_est = mcu.clock_est
+
def parse_line(self, line_num, line):
m = trapq_move_r.match(line)
if m is not None:
# Convert print_time to systime
- pt = float(m.group('time'))
+ pt = float(m.group("time"))
clock = pt * self.mcu_freq
sample_time, sample_clock, freq = self.clock_est
ts = sample_time + (clock - sample_clock) / freq
# Add systime to log
- parts = line.split(' ', 4)
+ parts = line.split(" ", 4)
parts[0] = "%s move" % (self.name,)
- parts[2] += '(%.6f)' % (ts,)
- self.trapq_stream.append((ts, line_num, ' '.join(parts)))
+ parts[2] += "(%.6f)" % (ts,)
+ self.trapq_stream.append((ts, line_num, " ".join(parts)))
return True, None
return False, None
+
def get_lines(self):
return self.trapq_stream
+
gcode_cmd_r = re.compile(r"^Read " + time_s + r": (?P<gcode>['\"].*)$")
varlist_split_r = re.compile(r"([^ ]+)=")
+
# G-Code shutdown message parsing
class GCodeStream:
def __init__(self, shutdown_line_num, logname):
self.gcode_stream = []
self.gcode_commands = []
- self.gcode_state = ''
+ self.gcode_state = ""
self.gcode_filename = "%s.gcode%05d" % (logname, shutdown_line_num)
+
def extract_params(self, line):
parts = varlist_split_r.split(line)
try:
- return { parts[i]: ast.literal_eval(parts[i+1].strip())
- for i in range(1, len(parts), 2) }
+ return {
+ parts[i]: ast.literal_eval(parts[i + 1].strip())
+ for i in range(1, len(parts), 2)
+ }
except:
return {}
+
def handle_gcode_state(self, line):
kv = self.extract_params(line)
- out = ['; Start g-code state restore', 'G28']
- if not kv.get('absolute_coord', kv.get('absolutecoord')):
- out.append('G91')
- if not kv.get('absolute_extrude', kv.get('absoluteextrude')):
- out.append('M83')
- lp = kv['last_position']
- out.append('G1 X%f Y%f Z%f F%f' % (
- lp[0], lp[1], lp[2], kv['speed'] * 60.))
- bp = kv['base_position']
- if bp[:3] != [0., 0., 0.]:
- out.append('; Must manually set base position...')
- out.append('G92 E%f' % (lp[3] - bp[3],))
- hp = kv['homing_position']
- if hp != [0., 0., 0., 0.]:
- out.append('; Must manually set homing position...')
- if abs(kv['speed_factor'] - 1. / 60.) > .000001:
- out.append('M220 S%f' % (kv['speed_factor'] * 60. * 100.,))
- if kv['extrude_factor'] != 1.:
- out.append('M221 S%f' % (kv['extrude_factor'] * 100.,))
- out.extend(['; End of state restore', '', ''])
- self.gcode_state = '\n'.join(out)
+ out = ["; Start g-code state restore", "G28"]
+ if not kv.get("absolute_coord", kv.get("absolutecoord")):
+ out.append("G91")
+ if not kv.get("absolute_extrude", kv.get("absoluteextrude")):
+ out.append("M83")
+ lp = kv["last_position"]
+ out.append("G1 X%f Y%f Z%f F%f" % (lp[0], lp[1], lp[2], kv["speed"] * 60.0))
+ bp = kv["base_position"]
+ if bp[:3] != [0.0, 0.0, 0.0]:
+ out.append("; Must manually set base position...")
+ out.append("G92 E%f" % (lp[3] - bp[3],))
+ hp = kv["homing_position"]
+ if hp != [0.0, 0.0, 0.0, 0.0]:
+ out.append("; Must manually set homing position...")
+ if abs(kv["speed_factor"] - 1.0 / 60.0) > 0.000001:
+ out.append("M220 S%f" % (kv["speed_factor"] * 60.0 * 100.0,))
+ if kv["extrude_factor"] != 1.0:
+ out.append("M221 S%f" % (kv["extrude_factor"] * 100.0,))
+ out.extend(["; End of state restore", "", ""])
+ self.gcode_state = "\n".join(out)
+
def parse_line(self, line_num, line):
m = gcode_cmd_r.match(line)
if m is not None:
- ts = float(m.group('time'))
+ ts = float(m.group("time"))
self.gcode_stream.append((ts, line_num, line))
- self.gcode_commands.append(m.group('gcode'))
+ self.gcode_commands.append(m.group("gcode"))
return True, None
return False, None
+
def get_lines(self):
# Produce output gcode stream
if self.gcode_stream:
data = (ast.literal_eval(gc) for gc in self.gcode_commands)
- with open(self.gcode_filename, 'wt') as f:
- f.write(self.gcode_state + ''.join(data))
+ with open(self.gcode_filename, "wt") as f:
+ f.write(self.gcode_state + "".join(data))
return self.gcode_stream
+
api_cmd_r = re.compile(r"^Received " + time_s + r": \{.*\}$")
+
# API server shutdowm message parsing
class APIStream:
def __init__(self):
self.api_stream = []
+
def parse_line(self, line_num, line):
m = api_cmd_r.match(line)
if m is not None:
- ts = float(m.group('time'))
+ ts = float(m.group("time"))
self.api_stream.append((ts, line_num, line))
return True, None
return False, None
+
def get_lines(self):
return self.api_stream
+
stats_r = re.compile(r"^Stats " + time_s + ": ")
mcu_r = re.compile(r"MCU '(?P<mcu>[^']+)' (is_)?shutdown: (?P<reason>.*)$")
-stepper_r = re.compile(r"^Dumping stepper '(?P<name>[^']*)' \((?P<mcu>[^)]+)\) "
- + count_s + r" queue_step:$")
-trapq_r = re.compile(r"^Dumping trapq '(?P<name>[^']*)' " + count_s
- + r" moves:$")
+stepper_r = re.compile(
+ r"^Dumping stepper '(?P<name>[^']*)' \((?P<mcu>[^)]+)\) "
+ + count_s
+ + r" queue_step:$"
+)
+trapq_r = re.compile(r"^Dumping trapq '(?P<name>[^']*)' " + count_s + r" moves:$")
gcode_r = re.compile(r"Dumping gcode input " + count_s + r" blocks$")
gcode_state_r = re.compile(r"^gcode state: ")
-api_r = re.compile(r"Dumping " + count_s + r" requests for client "
- + r"(?P<client>[0-9]+)" + r"$")
+api_r = re.compile(
+ r"Dumping " + count_s + r" requests for client " + r"(?P<client>[0-9]+)" + r"$"
+)
+
# Stats message parsing and high-level message dispatch
class StatsStream:
@@ -424,39 +515,49 @@ class StatsStream:
self.mcus = {}
self.first_stat_time = self.last_stat_time = None
self.stats_stream = []
+
def reset_first_stat_time(self):
self.first_stat_time = self.last_stat_time
+
def get_stat_times(self):
return self.first_stat_time, self.last_stat_time
+
def check_stats_seq(self, ts, line):
# Parse stats
parts = line.split()
mcu = ""
keyparts = {}
for p in parts[2:]:
- if '=' not in p:
+ if "=" not in p:
mcu = p
continue
- name, val = p.split('=', 1)
+ name, val = p.split("=", 1)
keyparts[mcu + name] = val
min_ts = 0
max_ts = 999999999999
for mcu_name, mcu in self.mcus.items():
- sname = '%s:send_seq' % (mcu_name,)
- rname = '%s:receive_seq' % (mcu_name,)
+ sname = "%s:send_seq" % (mcu_name,)
+ rname = "%s:receive_seq" % (mcu_name,)
if sname not in keyparts:
continue
sseq = int(keyparts[sname])
rseq = int(keyparts[rname])
- min_ts = max(min_ts, mcu.sent_seq_to_time.get(sseq-1, 0),
- mcu.receive_seq_to_time.get(rseq, 0))
- max_ts = min(max_ts, mcu.sent_seq_to_time.get(sseq, 999999999999),
- mcu.receive_seq_to_time.get(rseq+1, 999999999999))
+ min_ts = max(
+ min_ts,
+ mcu.sent_seq_to_time.get(sseq - 1, 0),
+ mcu.receive_seq_to_time.get(rseq, 0),
+ )
+ max_ts = min(
+ max_ts,
+ mcu.sent_seq_to_time.get(sseq, 999999999999),
+ mcu.receive_seq_to_time.get(rseq + 1, 999999999999),
+ )
return min(max(ts, min_ts + 0.00000001), max_ts - 0.00000001)
+
def parse_line(self, line_num, line):
m = stats_r.match(line)
if m is not None:
- ts = float(m.group('time'))
+ ts = float(m.group("time"))
self.last_stat_time = ts
if self.first_stat_time is None:
self.first_stat_time = ts
@@ -465,17 +566,16 @@ class StatsStream:
self.stats_stream.append((None, line_num, line))
m = mcu_r.match(line)
if m is not None:
- mcu_name = m.group('mcu')
+ mcu_name = m.group("mcu")
mcu_stream = MCUStream(mcu_name)
self.mcus[mcu_name] = mcu_stream
return True, mcu_stream
m = stepper_r.match(line)
if m is not None:
- return True, StepperStream(m.group('name'), m.group('mcu'),
- self.mcus)
+ return True, StepperStream(m.group("name"), m.group("mcu"), self.mcus)
m = trapq_r.match(line)
if m is not None:
- return True, TrapQStream(m.group('name'), self.mcus)
+ return True, TrapQStream(m.group("name"), self.mcus)
m = gcode_r.match(line)
if m is not None:
return True, self.gcode_stream
@@ -487,6 +587,7 @@ class StatsStream:
if m is not None:
return True, APIStream()
return False, None
+
def get_lines(self):
# Ignore old stats
all_ts = []
@@ -498,7 +599,7 @@ class StatsStream:
min_stream_ts = min(all_ts)
max_stream_ts = max(all_ts)
for i, info in enumerate(self.stats_stream):
- if info[0] is not None and info[0] >= min_stream_ts - 5.:
+ if info[0] is not None and info[0] >= min_stream_ts - 5.0:
del self.stats_stream[:i]
break
# Find the first stats timestamp
@@ -511,12 +612,12 @@ class StatsStream:
for i, (ts, line_num, line) in enumerate(self.stats_stream):
if ts is not None:
last_ts = self.check_stats_seq(ts, line)
- elif (line_num >= self.shutdown_line_num
- and last_ts <= max_stream_ts):
+ elif line_num >= self.shutdown_line_num and last_ts <= max_stream_ts:
last_ts = max_stream_ts + 0.00000001
self.stats_stream[i] = (last_ts, line_num, line)
return self.stats_stream
+
# Main handler for creating shutdown diagnostics file
class GatherShutdown:
def __init__(self, configs, line_num, recent_lines, logname):
@@ -533,21 +634,26 @@ class GatherShutdown:
for line_num, line in recent_lines:
self.parse_line(line_num, line)
self.stats_stream.reset_first_stat_time()
+
def add_comment(self, comment):
if comment is not None:
self.comments.append(comment)
+
def add_line(self, line_num, line):
self.parse_line(line_num, line)
first, last = self.stats_stream.get_stat_times()
- if first is not None and last > first + 5.:
+ if first is not None and last > first + 5.0:
self.finalize()
return False
- if (line.startswith('Git version')
- or line.startswith('Start printer at')
- or line == '===== Config file ====='):
+ if (
+ line.startswith("Git version")
+ or line.startswith("Start printer at")
+ or line == "===== Config file ====="
+ ):
self.finalize()
return False
return True
+
def parse_line(self, line_num, line):
for s in self.active_streams:
did_parse, new_stream = s.parse_line(line_num, line)
@@ -556,19 +662,20 @@ class GatherShutdown:
self.all_streams.append(new_stream)
self.active_streams = [new_stream, self.stats_stream]
break
+
def finalize(self):
# Make sure no timestamp goes backwards
streams = [p.get_lines() for p in self.all_streams]
for s in streams:
for i in range(1, len(s)):
- if s[i-1][0] > s[i][0]:
- s[i] = (s[i-1][0], s[i][1], s[i][2])
+ if s[i - 1][0] > s[i][0]:
+ s[i] = (s[i - 1][0], s[i][1], s[i][2])
# Produce output sorted by timestamp
out = [i for s in streams for i in s]
out.sort()
lines = itertools.chain(self.comments, (i[2] for i in out))
- lines = ('%s\n' % l for l in lines)
- with open(self.filename, 'wt') as f:
+ lines = ("%s\n" % l for l in lines)
+ with open(self.filename, "wt") as f:
f.writelines(lines)
@@ -576,6 +683,7 @@ class GatherShutdown:
# Startup
######################################################################
+
def main():
logname = sys.argv[1]
last_git = last_start = None
@@ -583,7 +691,7 @@ def main():
handler = None
recent_lines = collections.deque([], 200)
# Parse log file
- with open(logname, 'rt') as f:
+ with open(logname, "rt") as f:
for line_num, line in enumerate(f):
line = line.rstrip()
line_num += 1
@@ -594,18 +702,16 @@ def main():
continue
recent_lines.clear()
handler = None
- if line.startswith('Git version'):
+ if line.startswith("Git version"):
last_git = format_comment(line_num, line)
- elif line.startswith('Start printer at'):
+ elif line.startswith("Start printer at"):
last_start = format_comment(line_num, line)
- elif line == '===== Config file =====':
- handler = GatherConfig(configs, line_num,
- recent_lines, logname)
+ elif line == "===== Config file =====":
+ handler = GatherConfig(configs, line_num, recent_lines, logname)
handler.add_comment(last_git)
handler.add_comment(last_start)
- elif 'shutdown: ' in line or line.startswith('Dumping '):
- handler = GatherShutdown(configs, line_num,
- recent_lines, logname)
+ elif "shutdown: " in line or line.startswith("Dumping "):
+ handler = GatherShutdown(configs, line_num, recent_lines, logname)
handler.add_comment(last_git)
handler.add_comment(last_start)
if handler is not None:
@@ -614,5 +720,6 @@ def main():
for cfg in configs.values():
cfg.write_file()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/make_version.py b/scripts/make_version.py
index e64ef7d6..7a48018a 100644
--- a/scripts/make_version.py
+++ b/scripts/make_version.py
@@ -11,21 +11,21 @@ import argparse
import os
import sys
-sys.path.append(os.path.join(os.path.dirname(__file__), '../klippy'))
+sys.path.append(os.path.join(os.path.dirname(__file__), "../klippy"))
import util
def main(argv):
p = argparse.ArgumentParser()
- p.add_argument(
- 'distroname',
- help='Name of distro this package is intended for'
- )
+ p.add_argument("distroname", help="Name of distro this package is intended for")
args = p.parse_args()
- print(util.get_git_version(from_file=False)["version"],
- args.distroname.replace(' ', ''), sep='-')
+ print(
+ util.get_git_version(from_file=False)["version"],
+ args.distroname.replace(" ", ""),
+ sep="-",
+ )
-if __name__ == '__main__':
+if __name__ == "__main__":
main(sys.argv[1:])
diff --git a/scripts/motan/analyzers.py b/scripts/motan/analyzers.py
index 2796362f..917cb032 100644
--- a/scripts/motan/analyzers.py
+++ b/scripts/motan/analyzers.py
@@ -14,49 +14,57 @@ import readlog
# Analyzer handlers: {name: class, ...}
AHandlers = {}
+
# Calculate a derivative (position to velocity, or velocity to accel)
class GenDerivative:
ParametersMin = ParametersMax = 1
DataSets = [
- ('derivative(<dataset>)', 'Derivative of the given dataset'),
+ ("derivative(<dataset>)", "Derivative of the given dataset"),
]
+
def __init__(self, amanager, name_parts):
self.amanager = amanager
self.source = name_parts[1]
amanager.setup_dataset(self.source)
+
def get_label(self):
label = self.amanager.get_label(self.source)
- lname = label['label']
- units = label['units']
- if '(mm)' in units:
- rep = [('Position', 'Velocity'), ('(mm)', '(mm/s)')]
- elif '(mm/s)' in units:
- rep = [('Velocity', 'Acceleration'), ('(mm/s)', '(mm/s^2)')]
+ lname = label["label"]
+ units = label["units"]
+ if "(mm)" in units:
+ rep = [("Position", "Velocity"), ("(mm)", "(mm/s)")]
+ elif "(mm/s)" in units:
+ rep = [("Velocity", "Acceleration"), ("(mm/s)", "(mm/s^2)")]
else:
- return {'label': 'Derivative', 'units': 'Unknown'}
+ return {"label": "Derivative", "units": "Unknown"}
for old, new in rep:
lname = lname.replace(old, new).replace(old.lower(), new.lower())
units = units.replace(old, new).replace(old.lower(), new.lower())
- return {'label': lname, 'units': units}
+ return {"label": lname, "units": units}
+
def generate_data(self):
- inv_seg_time = 1. / self.amanager.get_segment_time()
+ inv_seg_time = 1.0 / self.amanager.get_segment_time()
data = self.amanager.get_datasets()[self.source]
- deriv = [(data[i+1] - data[i]) * inv_seg_time
- for i in range(len(data)-1)]
+ deriv = [(data[i + 1] - data[i]) * inv_seg_time for i in range(len(data) - 1)]
return [deriv[0]] + deriv
+
+
AHandlers["derivative"] = GenDerivative
+
# Calculate an integral (accel to velocity, or velocity to position)
class GenIntegral:
ParametersMin = 1
ParametersMax = 3
DataSets = [
- ('integral(<dataset>)', 'Integral of the given dataset'),
- ('integral(<dataset1>,<dataset2>)',
- 'Integral with dataset2 as reference'),
- ('integral(<dataset1>,<dataset2>,<half_life>)',
- 'Integral with weighted half-life time'),
+ ("integral(<dataset>)", "Integral of the given dataset"),
+ ("integral(<dataset1>,<dataset2>)", "Integral with dataset2 as reference"),
+ (
+ "integral(<dataset1>,<dataset2>,<half_life>)",
+ "Integral with weighted half-life time",
+ ),
]
+
def __init__(self, amanager, name_parts):
self.amanager = amanager
self.source = name_parts[1]
@@ -68,54 +76,58 @@ class GenIntegral:
amanager.setup_dataset(self.ref)
if len(name_parts) == 4:
self.half_life = float(name_parts[3])
+
def get_label(self):
label = self.amanager.get_label(self.source)
- lname = label['label']
- units = label['units']
- if '(mm/s)' in units:
- rep = [('Velocity', 'Position'), ('(mm/s)', '(mm)')]
- elif '(mm/s^2)' in units:
- rep = [('Acceleration', 'Velocity'), ('(mm/s^2)', '(mm/s)')]
+ lname = label["label"]
+ units = label["units"]
+ if "(mm/s)" in units:
+ rep = [("Velocity", "Position"), ("(mm/s)", "(mm)")]
+ elif "(mm/s^2)" in units:
+ rep = [("Acceleration", "Velocity"), ("(mm/s^2)", "(mm/s)")]
else:
- return {'label': 'Integral', 'units': 'Unknown'}
+ return {"label": "Integral", "units": "Unknown"}
for old, new in rep:
lname = lname.replace(old, new).replace(old.lower(), new.lower())
units = units.replace(old, new).replace(old.lower(), new.lower())
- return {'label': lname, 'units': units}
+ return {"label": lname, "units": units}
+
def generate_data(self):
seg_time = self.amanager.get_segment_time()
src = self.amanager.get_datasets()[self.source]
offset = sum(src) / len(src)
- total = 0.
+ total = 0.0
ref = None
if self.ref is not None:
ref = self.amanager.get_datasets()[self.ref]
offset -= (ref[-1] - ref[0]) / (len(src) * seg_time)
total = ref[0]
- src_weight = 1.
+ src_weight = 1.0
if self.half_life:
- src_weight = math.exp(math.log(.5) * seg_time / self.half_life)
- ref_weight = 1. - src_weight
- data = [0.] * len(src)
+ src_weight = math.exp(math.log(0.5) * seg_time / self.half_life)
+ ref_weight = 1.0 - src_weight
+ data = [0.0] * len(src)
for i, v in enumerate(src):
total += (v - offset) * seg_time
if ref is not None:
total = src_weight * total + ref_weight * ref[i]
data[i] = total
return data
+
+
AHandlers["integral"] = GenIntegral
+
# Calculate a pointwise 2-norm of several datasets (e.g. compute velocity or
# accel from its x, y,... components)
class GenNorm2:
ParametersMin = 2
ParametersMax = 3
DataSets = [
- ('norm2(<dataset1>,<dataset2>)',
- 'pointwise 2-norm of dataset1 and dataset2'),
- ('norm2(<dataset1>,<dataset2>,<dataset3>)',
- 'pointwise 2-norm of 3 datasets'),
+ ("norm2(<dataset1>,<dataset2>)", "pointwise 2-norm of dataset1 and dataset2"),
+ ("norm2(<dataset1>,<dataset2>,<dataset3>)", "pointwise 2-norm of 3 datasets"),
]
+
def __init__(self, amanager, name_parts):
self.amanager = amanager
self.datasets = []
@@ -125,48 +137,56 @@ class GenNorm2:
self.datasets.append(name_parts[3])
for dataset in self.datasets:
amanager.setup_dataset(dataset)
+
def get_label(self):
label = self.amanager.get_label(self.datasets[0])
- units = label['units']
- datas = ['position', 'velocity', 'acceleration']
- data_name = ''
+ units = label["units"]
+ datas = ["position", "velocity", "acceleration"]
+ data_name = ""
for d in datas:
- if d in label['label']:
+ if d in label["label"]:
data_name = d
break
- lname = ''
+ lname = ""
for d in self.datasets:
- l = self.amanager.get_label(d)['label']
+ l = self.amanager.get_label(d)["label"]
for r in datas:
- l = l.replace(r, '').strip()
+ l = l.replace(r, "").strip()
if lname:
- lname += '+'
+ lname += "+"
lname += l
- lname += ' ' + data_name + ' norm2'
- return {'label': lname, 'units': units}
+ lname += " " + data_name + " norm2"
+ return {"label": lname, "units": units}
+
def generate_data(self):
seg_time = self.amanager.get_segment_time()
data = []
for dataset in self.datasets:
data.append(self.amanager.get_datasets()[dataset])
- res = [0.] * len(data[0])
+ res = [0.0] * len(data[0])
for i in range(len(data[0])):
- norm2 = 0.
+ norm2 = 0.0
for dataset in data:
norm2 += dataset[i] * dataset[i]
res[i] = math.sqrt(norm2)
return res
+
+
AHandlers["norm2"] = GenNorm2
+
class GenSmoothed:
ParametersMin = 1
ParametersMax = 2
DataSets = [
- ('smooth(<dataset>)', 'Generate moving weighted average of a dataset'),
- ('smooth(<dataset>,<smooth_time>)',
- 'Generate moving weighted average of a dataset with a given'
- ' smoothing time that defines the window size'),
+ ("smooth(<dataset>)", "Generate moving weighted average of a dataset"),
+ (
+ "smooth(<dataset>,<smooth_time>)",
+ "Generate moving weighted average of a dataset with a given"
+ " smoothing time that defines the window size",
+ ),
]
+
def __init__(self, amanager, name_parts):
self.amanager = amanager
self.source = name_parts[1]
@@ -174,125 +194,152 @@ class GenSmoothed:
self.smooth_time = 0.01
if len(name_parts) > 2:
self.smooth_time = float(name_parts[2])
+
def get_label(self):
label = self.amanager.get_label(self.source)
- return {'label': 'Smoothed ' + label['label'], 'units': label['units']}
+ return {"label": "Smoothed " + label["label"], "units": label["units"]}
+
def generate_data(self):
seg_time = self.amanager.get_segment_time()
src = self.amanager.get_datasets()[self.source]
n = len(src)
- data = [0.] * n
+ data = [0.0] * n
hst = 0.5 * self.smooth_time
seg_half_len = round(hst / seg_time)
- inv_norm = 1. / sum([min(k + 1, seg_half_len + seg_half_len - k)
- for k in range(2 * seg_half_len)])
+ inv_norm = 1.0 / sum(
+ [
+ min(k + 1, seg_half_len + seg_half_len - k)
+ for k in range(2 * seg_half_len)
+ ]
+ )
for i in range(n):
j = max(0, i - seg_half_len)
je = min(n, i + seg_half_len)
- avg_val = 0.
+ avg_val = 0.0
for k, v in enumerate(src[j:je]):
avg_val += v * min(k + 1, seg_half_len + seg_half_len - k)
data[i] = avg_val * inv_norm
return data
+
+
AHandlers["smooth"] = GenSmoothed
+
# Calculate a kinematic stepper position from the toolhead requested position
class GenKinematicPosition:
ParametersMin = ParametersMax = 1
DataSets = [
- ('kin(<stepper>)', 'Stepper position derived from toolhead kinematics'),
+ ("kin(<stepper>)", "Stepper position derived from toolhead kinematics"),
]
+
def __init__(self, amanager, name_parts):
self.amanager = amanager
stepper = name_parts[1]
status = self.amanager.get_initial_status()
- kin = status['configfile']['settings']['printer']['kinematics']
- if kin not in ['cartesian', 'corexy']:
+ kin = status["configfile"]["settings"]["printer"]["kinematics"]
+ if kin not in ["cartesian", "corexy"]:
raise amanager.error("Unsupported kinematics '%s'" % (kin,))
- if stepper not in ['stepper_x', 'stepper_y', 'stepper_z']:
+ if stepper not in ["stepper_x", "stepper_y", "stepper_z"]:
raise amanager.error("Unknown stepper '%s'" % (stepper,))
- if kin == 'corexy' and stepper in ['stepper_x', 'stepper_y']:
- self.source1 = 'trapq(toolhead,x)'
- self.source2 = 'trapq(toolhead,y)'
- if stepper == 'stepper_x':
+ if kin == "corexy" and stepper in ["stepper_x", "stepper_y"]:
+ self.source1 = "trapq(toolhead,x)"
+ self.source2 = "trapq(toolhead,y)"
+ if stepper == "stepper_x":
self.generate_data = self.generate_data_corexy_plus
else:
self.generate_data = self.generate_data_corexy_minus
amanager.setup_dataset(self.source1)
amanager.setup_dataset(self.source2)
else:
- self.source1 = 'trapq(toolhead,%s)' % (stepper[-1:],)
+ self.source1 = "trapq(toolhead,%s)" % (stepper[-1:],)
self.source2 = None
self.generate_data = self.generate_data_passthrough
amanager.setup_dataset(self.source1)
+
def get_label(self):
- return {'label': 'Position', 'units': 'Position\n(mm)'}
+ return {"label": "Position", "units": "Position\n(mm)"}
+
def generate_data_corexy_plus(self):
datasets = self.amanager.get_datasets()
data1 = datasets[self.source1]
data2 = datasets[self.source2]
return [d1 + d2 for d1, d2 in zip(data1, data2)]
+
def generate_data_corexy_minus(self):
datasets = self.amanager.get_datasets()
data1 = datasets[self.source1]
data2 = datasets[self.source2]
return [d1 - d2 for d1, d2 in zip(data1, data2)]
+
def generate_data_passthrough(self):
return self.amanager.get_datasets()[self.source1]
+
+
AHandlers["kin"] = GenKinematicPosition
+
# Calculate a toolhead x/y position from corexy stepper positions
class GenCorexyPosition:
ParametersMin = ParametersMax = 3
DataSets = [
- ('corexy(x,<stepper>,<stepper>)', 'Toolhead x position from steppers'),
- ('corexy(y,<stepper>,<stepper>)', 'Toolhead y position from steppers'),
+ ("corexy(x,<stepper>,<stepper>)", "Toolhead x position from steppers"),
+ ("corexy(y,<stepper>,<stepper>)", "Toolhead y position from steppers"),
]
+
def __init__(self, amanager, name_parts):
self.amanager = amanager
- self.is_plus = name_parts[1] == 'x'
+ self.is_plus = name_parts[1] == "x"
self.source1, self.source2 = name_parts[2:]
amanager.setup_dataset(self.source1)
amanager.setup_dataset(self.source2)
+
def get_label(self):
- axis = 'x'
+ axis = "x"
if not self.is_plus:
- axis = 'y'
- return {'label': 'Derived %s position' % (axis,),
- 'units': 'Position\n(mm)'}
+ axis = "y"
+ return {"label": "Derived %s position" % (axis,), "units": "Position\n(mm)"}
+
def generate_data(self):
datasets = self.amanager.get_datasets()
data1 = datasets[self.source1]
data2 = datasets[self.source2]
if self.is_plus:
- return [.5 * (d1 + d2) for d1, d2 in zip(data1, data2)]
- return [.5 * (d1 - d2) for d1, d2 in zip(data1, data2)]
+ return [0.5 * (d1 + d2) for d1, d2 in zip(data1, data2)]
+ return [0.5 * (d1 - d2) for d1, d2 in zip(data1, data2)]
+
+
AHandlers["corexy"] = GenCorexyPosition
+
# Calculate a position deviation
class GenDeviation:
ParametersMin = ParametersMax = 2
DataSets = [
- ('deviation(<dataset1>,<dataset2>)', 'Difference between datasets'),
+ ("deviation(<dataset1>,<dataset2>)", "Difference between datasets"),
]
+
def __init__(self, amanager, name_parts):
self.amanager = amanager
self.source1, self.source2 = name_parts[1:]
amanager.setup_dataset(self.source1)
amanager.setup_dataset(self.source2)
+
def get_label(self):
label1 = self.amanager.get_label(self.source1)
label2 = self.amanager.get_label(self.source2)
- if label1['units'] != label2['units']:
- return {'label': 'Deviation', 'units': 'Unknown'}
- parts = label1['units'].split('\n')
- units = '\n'.join([parts[0]] + ['Deviation'] + parts[1:])
- return {'label': label1['label'] + ' deviation', 'units': units}
+ if label1["units"] != label2["units"]:
+ return {"label": "Deviation", "units": "Unknown"}
+ parts = label1["units"].split("\n")
+ units = "\n".join([parts[0]] + ["Deviation"] + parts[1:])
+ return {"label": label1["label"] + " deviation", "units": units}
+
def generate_data(self):
datasets = self.amanager.get_datasets()
data1 = datasets[self.source1]
data2 = datasets[self.source2]
return [d1 - d2 for d1, d2 in zip(data1, data2)]
+
+
AHandlers["deviation"] = GenDeviation
@@ -300,6 +347,7 @@ AHandlers["deviation"] = GenDeviation
# Analyzer management and data generation
######################################################################
+
# Return a description of available analyzers
def list_datasets():
datasets = []
@@ -307,9 +355,11 @@ def list_datasets():
datasets += AHandlers[ah].DataSets
return datasets
+
# Manage raw and generated data samples
class AnalyzerManager:
error = None
+
def __init__(self, lmanager, segment_time):
self.lmanager = lmanager
self.error = lmanager.error
@@ -318,17 +368,23 @@ class AnalyzerManager:
self.gen_datasets = collections.OrderedDict()
self.datasets = {}
self.dataset_times = []
- self.duration = 5.
+ self.duration = 5.0
+
def set_duration(self, duration):
self.duration = duration
+
def get_segment_time(self):
return self.segment_time
+
def get_datasets(self):
return self.datasets
+
def get_dataset_times(self):
return self.dataset_times
+
def get_initial_status(self):
return self.lmanager.get_initial_status()
+
def setup_dataset(self, name):
name = name.strip()
if name in self.raw_datasets:
@@ -350,6 +406,7 @@ class AnalyzerManager:
self.gen_datasets[name] = hdl
self.datasets[name] = []
return hdl
+
def get_label(self, dataset):
hdl = self.raw_datasets.get(dataset)
if hdl is None:
@@ -357,10 +414,12 @@ class AnalyzerManager:
if hdl is None:
raise self.error("Unknown dataset '%s'" % (dataset,))
return hdl.get_label()
+
def generate_datasets(self):
# Generate raw data
- list_hdls = [(self.datasets[name], hdl)
- for name, hdl in self.raw_datasets.items()]
+ list_hdls = [
+ (self.datasets[name], hdl) for name, hdl in self.raw_datasets.items()
+ ]
initial_start_time = self.lmanager.get_initial_start_time()
start_time = t = self.lmanager.get_start_time()
end_time = start_time + self.duration
diff --git a/scripts/motan/data_logger.py b/scripts/motan/data_logger.py
index fd4de7a5..00023c2f 100755
--- a/scripts/motan/data_logger.py
+++ b/scripts/motan/data_logger.py
@@ -7,7 +7,8 @@
import sys, os, optparse, socket, select, json, errno, time, zlib
INDEX_UPDATE_TIME = 5.0
-ClientInfo = {'program': 'motan_data_logger', 'version': 'v0.1'}
+ClientInfo = {"program": "motan_data_logger", "version": "v0.1"}
+
def webhook_socket_create(uds_filename):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -20,25 +21,28 @@ def webhook_socket_create(uds_filename):
if e.errno == errno.ECONNREFUSED:
time.sleep(0.1)
continue
- sys.stderr.write("Unable to connect socket %s [%d,%s]\n"
- % (uds_filename, e.errno,
- errno.errorcode[e.errno]))
+ sys.stderr.write(
+ "Unable to connect socket %s [%d,%s]\n"
+ % (uds_filename, e.errno, errno.errorcode[e.errno])
+ )
sys.exit(-1)
break
sys.stderr.write("Connection.\n")
return sock
+
class LogWriter:
def __init__(self, filename):
self.file = open(filename, "wb")
- self.comp = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
- zlib.DEFLATED, 31)
+ self.comp = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, 31)
self.raw_pos = self.file_pos = 0
+
def add_data(self, data):
d = self.comp.compress(data + b"\x03")
self.file.write(d)
self.file_pos += len(d)
self.raw_pos += len(data) + 1
+
def flush(self, flag=zlib.Z_FULL_FLUSH):
if not self.raw_pos:
return self.file_pos
@@ -46,12 +50,14 @@ class LogWriter:
self.file.write(d)
self.file_pos += len(d)
return self.file_pos
+
def close(self):
self.flush(zlib.Z_FINISH)
self.file.close()
self.file = None
self.comp = None
+
class DataLogger:
def __init__(self, uds_filename, log_prefix):
# IO
@@ -67,23 +73,26 @@ class DataLogger:
self.async_handlers = {}
# get_status databasing
self.db = {}
- self.next_index_time = 0.
+ self.next_index_time = 0.0
# Start login process
- self.send_query("info", "info", {"client_info": ClientInfo},
- self.handle_info)
+ self.send_query("info", "info", {"client_info": ClientInfo}, self.handle_info)
+
def error(self, msg):
sys.stderr.write(msg + "\n")
+
def finish(self, msg):
self.error(msg)
self.logger.close()
self.index.close()
sys.exit(0)
+
# Unix Domain Socket IO
def send_query(self, msg_id, method, params, cb):
self.query_handlers[msg_id] = cb
msg = {"id": msg_id, "method": method, "params": params}
- cm = json.dumps(msg, separators=(',', ':')).encode()
+ cm = json.dumps(msg, separators=(",", ":")).encode()
self.webhook_socket.send(cm + b"\x03")
+
def process_socket(self):
data = self.webhook_socket.recv(4096)
if not data:
@@ -113,15 +122,17 @@ class DataLogger:
self.flush_index()
continue
self.error("ERROR: Message with unknown id")
+
def run(self):
try:
while 1:
- res = self.poll.poll(1000.)
+ res = self.poll.poll(1000.0)
for fd, event in res:
if fd == self.webhook_socket.fileno():
self.process_socket()
except KeyboardInterrupt as e:
self.finish("Keyboard Interrupt")
+
# Query response handlers
def send_subscribe(self, msg_id, method, params, cb=None, async_cb=None):
if cb is None:
@@ -130,14 +141,22 @@ class DataLogger:
self.async_handlers[msg_id] = async_cb
params["response_template"] = {"q": msg_id}
self.send_query(msg_id, method, params, cb)
+
def handle_info(self, msg, raw_msg):
if msg["result"]["state"] != "ready":
self.finish("Klipper not in ready state")
self.send_query("list", "objects/list", {}, self.handle_list)
+
def handle_list(self, msg, raw_msg):
subreq = {o: None for o in msg["result"]["objects"]}
- self.send_subscribe("status", "objects/subscribe", {"objects": subreq},
- self.handle_subscribe, self.handle_async_db)
+ self.send_subscribe(
+ "status",
+ "objects/subscribe",
+ {"objects": subreq},
+ self.handle_subscribe,
+ self.handle_async_db,
+ )
+
def handle_subscribe(self, msg, raw_msg):
result = msg["result"]
self.next_index_time = result["eventtime"] + INDEX_UPDATE_TIME
@@ -145,15 +164,17 @@ class DataLogger:
# Subscribe to trapq and stepper queue updates
motion_report = status.get("motion_report", {})
for trapq in motion_report.get("trapq", []):
- self.send_subscribe("trapq:" + trapq, "motion_report/dump_trapq",
- {"name": trapq})
+ self.send_subscribe(
+ "trapq:" + trapq, "motion_report/dump_trapq", {"name": trapq}
+ )
for stepper in motion_report.get("steppers", []):
- self.send_subscribe("stepq:" + stepper,
- "motion_report/dump_stepper", {"name": stepper})
+ self.send_subscribe(
+ "stepq:" + stepper, "motion_report/dump_stepper", {"name": stepper}
+ )
# Subscribe to additional sensor data
stypes = ["adxl345", "lis2dw", "mpu9250", "angle"]
- stypes = {st:st for st in stypes}
- stypes['probe_eddy_current'] = 'ldc1612'
+ stypes = {st: st for st in stypes}
+ stypes["probe_eddy_current"] = "ldc1612"
config = status["configfile"]["settings"]
for cfgname in config.keys():
for capprefix, st in sorted(stypes.items()):
@@ -163,30 +184,37 @@ class DataLogger:
qcmd = "%s/dump_%s" % (st, st)
self.send_subscribe(lname, qcmd, {"sensor": aname})
if cfgname.startswith("tmc"):
- driver = ' '.join(cfgname.split()[1:])
- self.send_subscribe("stallguard:" + driver,
- "tmc/stallguard_dump", {"name": driver})
+ driver = " ".join(cfgname.split()[1:])
+ self.send_subscribe(
+ "stallguard:" + driver, "tmc/stallguard_dump", {"name": driver}
+ )
+
def handle_dump(self, msg, raw_msg):
msg_id = msg["id"]
if "result" not in msg:
- self.error("Unable to subscribe to '%s': %s"
- % (msg_id, msg.get("error", {}).get("message", "")))
+ self.error(
+ "Unable to subscribe to '%s': %s"
+ % (msg_id, msg.get("error", {}).get("message", ""))
+ )
return
self.db.setdefault("subscriptions", {})[msg_id] = msg["result"]
+
def flush_index(self):
- self.db['file_position'] = self.logger.flush()
- self.index.add_data(json.dumps(self.db, separators=(',', ':')).encode())
+ self.db["file_position"] = self.logger.flush()
+ self.index.add_data(json.dumps(self.db, separators=(",", ":")).encode())
self.db = {"status": {}}
+
def handle_async_db(self, msg, raw_msg):
params = msg["params"]
- db_status = self.db['status']
+ db_status = self.db["status"]
for k, v in params.get("status", {}).items():
db_status.setdefault(k, {}).update(v)
- eventtime = params['eventtime']
+ eventtime = params["eventtime"]
if eventtime >= self.next_index_time:
self.next_index_time = eventtime + INDEX_UPDATE_TIME
self.flush_index()
+
def nice():
try:
# Try to re-nice writing process
@@ -194,6 +222,7 @@ def nice():
except:
pass
+
def main():
usage = "%prog [options] <socket filename> <log name>"
opts = optparse.OptionParser(usage)
@@ -205,5 +234,6 @@ def main():
dl = DataLogger(args[0], args[1])
dl.run()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/motan/motan_graph.py b/scripts/motan/motan_graph.py
index fc1dee17..8d551340 100755
--- a/scripts/motan/motan_graph.py
+++ b/scripts/motan/motan_graph.py
@@ -7,6 +7,7 @@
import sys, optparse, ast
import matplotlib
import readlog, analyzers
+
try:
import urlparse
except:
@@ -17,6 +18,7 @@ except:
# Graphing
######################################################################
+
def plot_motion(amanager, graphs, log_prefix):
# Generate data
for graph in graphs:
@@ -27,7 +29,7 @@ def plot_motion(amanager, graphs, log_prefix):
times = amanager.get_dataset_times()
# Build plot
fontP = matplotlib.font_manager.FontProperties()
- fontP.set_size('x-small')
+ fontP.set_size("x-small")
fig, rows = matplotlib.pyplot.subplots(nrows=len(graphs), sharex=True)
if len(graphs) == 1:
rows = [rows]
@@ -38,29 +40,29 @@ def plot_motion(amanager, graphs, log_prefix):
label = amanager.get_label(dataset)
ax = graph_ax
if graph_units is None:
- graph_units = label['units']
+ graph_units = label["units"]
ax.set_ylabel(graph_units)
- elif label['units'] != graph_units:
+ elif label["units"] != graph_units:
if graph_twin_units is None:
ax = twin_ax = graph_ax.twinx()
- graph_twin_units = label['units']
+ graph_twin_units = label["units"]
ax.set_ylabel(graph_twin_units)
- elif label['units'] == graph_twin_units:
+ elif label["units"] == graph_twin_units:
ax = twin_ax
else:
graph_units = "Unknown"
ax.set_ylabel(graph_units)
- pparams = {'label': label['label'], 'alpha': 0.8}
+ pparams = {"label": label["label"], "alpha": 0.8}
pparams.update(plot_params)
ax.plot(times, datasets[dataset], **pparams)
if twin_ax is not None:
li1, la1 = graph_ax.get_legend_handles_labels()
li2, la2 = twin_ax.get_legend_handles_labels()
- twin_ax.legend(li1 + li2, la1 + la2, loc='best', prop=fontP)
+ twin_ax.legend(li1 + li2, la1 + la2, loc="best", prop=fontP)
else:
- graph_ax.legend(loc='best', prop=fontP)
+ graph_ax.legend(loc="best", prop=fontP)
graph_ax.grid(True)
- rows[-1].set_xlabel('Time (s)')
+ rows[-1].set_xlabel("Time (s)")
return fig
@@ -68,23 +70,26 @@ def plot_motion(amanager, graphs, log_prefix):
# Startup
######################################################################
+
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
- matplotlib.use('Agg')
+ matplotlib.use("Agg")
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
+
def parse_graph_description(desc):
- if '?' not in desc:
+ if "?" not in desc:
return (desc, {})
- dataset, params = desc.split('?', 1)
+ dataset, params = desc.split("?", 1)
params = {k: v for k, v in urlparse.parse_qsl(params)}
- for fkey in ['alpha']:
+ for fkey in ["alpha"]:
if fkey in params:
params[fkey] = float(params[fkey])
return (dataset, params)
+
def list_datasets():
datasets = readlog.list_datasets() + analyzers.list_datasets()
out = ["\nAvailable datasets:\n"]
@@ -94,21 +99,35 @@ def list_datasets():
sys.stdout.write("".join(out))
sys.exit(0)
+
def main():
# Parse command-line arguments
usage = "%prog [options] <logname>"
opts = optparse.OptionParser(usage)
- opts.add_option("-o", "--output", type="string", dest="output",
- default=None, help="filename of output graph")
- opts.add_option("-s", "--skip", type="float", default=0.,
- help="Set the start time to graph")
- opts.add_option("-d", "--duration", type="float", default=5.,
- help="Number of seconds to graph")
- opts.add_option("--segment-time", type="float", default=0.000100,
- help="Analysis segment time (default 0.000100 seconds)")
+ opts.add_option(
+ "-o",
+ "--output",
+ type="string",
+ dest="output",
+ default=None,
+ help="filename of output graph",
+ )
+ opts.add_option(
+ "-s", "--skip", type="float", default=0.0, help="Set the start time to graph"
+ )
+ opts.add_option(
+ "-d", "--duration", type="float", default=5.0, help="Number of seconds to graph"
+ )
+ opts.add_option(
+ "--segment-time",
+ type="float",
+ default=0.000100,
+ help="Analysis segment time (default 0.000100 seconds)",
+ )
opts.add_option("-g", "--graph", help="Graph to generate (python literal)")
- opts.add_option("-l", "--list-datasets", action="store_true",
- help="List available datasets")
+ opts.add_option(
+ "-l", "--list-datasets", action="store_true", help="List available datasets"
+ )
options, args = opts.parse_args()
if options.list_datasets:
list_datasets()
@@ -131,8 +150,9 @@ def main():
]
if options.graph is not None:
graph_descs = ast.literal_eval(options.graph)
- graphs = [[parse_graph_description(g) for g in graph_row]
- for graph_row in graph_descs]
+ graphs = [
+ [parse_graph_description(g) for g in graph_row] for graph_row in graph_descs
+ ]
# Draw graph
setup_matplotlib(options.output is not None)
@@ -145,5 +165,6 @@ def main():
fig.set_size_inches(8, 6)
fig.savefig(options.output)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/motan/readlog.py b/scripts/motan/readlog.py
index 43c01619..8f86d4e2 100644
--- a/scripts/motan/readlog.py
+++ b/scripts/motan/readlog.py
@@ -5,6 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import json, zlib
+
class error(Exception):
pass
@@ -16,81 +17,98 @@ class error(Exception):
# Log data handlers: {name: class, ...}
LogHandlers = {}
+
# Extract status fields from log
class HandleStatusField:
SubscriptionIdParts = 0
ParametersMin = ParametersMax = 1
DataSets = [
- ('status(<field>)', 'A get_status field name (separate by periods)'),
+ ("status(<field>)", "A get_status field name (separate by periods)"),
]
+
def __init__(self, lmanager, name, name_parts):
self.status_tracker = lmanager.get_status_tracker()
self.field_name = name_parts[1]
- self.field_parts = name_parts[1].split('.')
- self.next_update_time = 0.
+ self.field_parts = name_parts[1].split(".")
+ self.next_update_time = 0.0
self.result = None
+
def get_label(self):
- label = '%s field' % (self.field_name,)
- return {'label': label, 'units': 'Unknown'}
+ label = "%s field" % (self.field_name,)
+ return {"label": label, "units": "Unknown"}
+
def pull_data(self, req_time):
if req_time < self.next_update_time:
return self.result
db, next_update_time = self.status_tracker.pull_status(req_time)
for fp in self.field_parts[:-1]:
db = db.get(fp, {})
- self.result = db.get(self.field_parts[-1], 0.)
+ self.result = db.get(self.field_parts[-1], 0.0)
self.next_update_time = next_update_time
return self.result
+
+
LogHandlers["status"] = HandleStatusField
+
# Extract requested position, velocity, and accel from a trapq log
class HandleTrapQ:
SubscriptionIdParts = 2
ParametersMin = ParametersMax = 2
DataSets = [
- ('trapq(<name>,velocity)', 'Requested velocity for the given trapq'),
- ('trapq(<name>,accel)', 'Requested acceleration for the given trapq'),
- ('trapq(<name>,<axis>)', 'Requested axis (x, y, or z) position'),
- ('trapq(<name>,<axis>_velocity)', 'Requested axis velocity'),
- ('trapq(<name>,<axis>_accel)', 'Requested axis acceleration'),
+ ("trapq(<name>,velocity)", "Requested velocity for the given trapq"),
+ ("trapq(<name>,accel)", "Requested acceleration for the given trapq"),
+ ("trapq(<name>,<axis>)", "Requested axis (x, y, or z) position"),
+ ("trapq(<name>,<axis>_velocity)", "Requested axis velocity"),
+ ("trapq(<name>,<axis>_accel)", "Requested axis acceleration"),
]
+
def __init__(self, lmanager, name, name_parts):
self.name = name
self.jdispatch = lmanager.get_jdispatch()
- self.cur_data = [(0., 0., 0., 0., (0., 0., 0.), (0., 0., 0.))]
+ self.cur_data = [(0.0, 0.0, 0.0, 0.0, (0.0, 0.0, 0.0), (0.0, 0.0, 0.0))]
self.data_pos = 0
tq, trapq_name, datasel = name_parts
ptypes = {}
- ptypes['velocity'] = {
- 'label': '%s velocity' % (trapq_name,),
- 'units': 'Velocity\n(mm/s)', 'func': self._pull_velocity
+ ptypes["velocity"] = {
+ "label": "%s velocity" % (trapq_name,),
+ "units": "Velocity\n(mm/s)",
+ "func": self._pull_velocity,
}
- ptypes['accel'] = {
- 'label': '%s acceleration' % (trapq_name,),
- 'units': 'Acceleration\n(mm/s^2)', 'func': self._pull_accel
+ ptypes["accel"] = {
+ "label": "%s acceleration" % (trapq_name,),
+ "units": "Acceleration\n(mm/s^2)",
+ "func": self._pull_accel,
}
for axis, name in enumerate("xyz"):
- ptypes['%s' % (name,)] = {
- 'label': '%s %s position' % (trapq_name, name), 'axis': axis,
- 'units': 'Position\n(mm)', 'func': self._pull_axis_position
+ ptypes["%s" % (name,)] = {
+ "label": "%s %s position" % (trapq_name, name),
+ "axis": axis,
+ "units": "Position\n(mm)",
+ "func": self._pull_axis_position,
}
- ptypes['%s_velocity' % (name,)] = {
- 'label': '%s %s velocity' % (trapq_name, name), 'axis': axis,
- 'units': 'Velocity\n(mm/s)', 'func': self._pull_axis_velocity
+ ptypes["%s_velocity" % (name,)] = {
+ "label": "%s %s velocity" % (trapq_name, name),
+ "axis": axis,
+ "units": "Velocity\n(mm/s)",
+ "func": self._pull_axis_velocity,
}
- ptypes['%s_accel' % (name,)] = {
- 'label': '%s %s acceleration' % (trapq_name, name),
- 'axis': axis, 'units': 'Acceleration\n(mm/s^2)',
- 'func': self._pull_axis_accel
+ ptypes["%s_accel" % (name,)] = {
+ "label": "%s %s acceleration" % (trapq_name, name),
+ "axis": axis,
+ "units": "Acceleration\n(mm/s^2)",
+ "func": self._pull_axis_accel,
}
pinfo = ptypes.get(datasel)
if pinfo is None:
raise error("Unknown trapq data selection '%s'" % (datasel,))
- self.label = {'label': pinfo['label'], 'units': pinfo['units']}
- self.axis = pinfo.get('axis')
- self.pull_data = pinfo['func']
+ self.label = {"label": pinfo["label"], "units": pinfo["units"]}
+ self.axis = pinfo.get("axis")
+ self.pull_data = pinfo["func"]
+
def get_label(self):
return self.label
+
def _find_move(self, req_time):
data_pos = self.data_pos
while 1:
@@ -105,54 +123,63 @@ class HandleTrapQ:
jmsg = self.jdispatch.pull_msg(req_time, self.name)
if jmsg is None:
return move, False
- self.cur_data = jmsg['data']
+ self.cur_data = jmsg["data"]
self.data_pos = data_pos = 0
+
def _pull_axis_position(self, req_time):
move, in_range = self._find_move(req_time)
print_time, move_t, start_v, accel, start_pos, axes_r = move
- mtime = max(0., min(move_t, req_time - print_time))
- dist = (start_v + .5 * accel * mtime) * mtime;
+ mtime = max(0.0, min(move_t, req_time - print_time))
+ dist = (start_v + 0.5 * accel * mtime) * mtime
return start_pos[self.axis] + axes_r[self.axis] * dist
+
def _pull_axis_velocity(self, req_time):
move, in_range = self._find_move(req_time)
if not in_range:
- return 0.
+ return 0.0
print_time, move_t, start_v, accel, start_pos, axes_r = move
return (start_v + accel * (req_time - print_time)) * axes_r[self.axis]
+
def _pull_axis_accel(self, req_time):
move, in_range = self._find_move(req_time)
if not in_range:
- return 0.
+ return 0.0
print_time, move_t, start_v, accel, start_pos, axes_r = move
return accel * axes_r[self.axis]
+
def _pull_velocity(self, req_time):
move, in_range = self._find_move(req_time)
if not in_range:
- return 0.
+ return 0.0
print_time, move_t, start_v, accel, start_pos, axes_r = move
return start_v + accel * (req_time - print_time)
+
def _pull_accel(self, req_time):
move, in_range = self._find_move(req_time)
if not in_range:
- return 0.
+ return 0.0
print_time, move_t, start_v, accel, start_pos, axes_r = move
return accel
+
+
LogHandlers["trapq"] = HandleTrapQ
+
# Extract positions from queue_step log
class HandleStepQ:
SubscriptionIdParts = 2
ParametersMin = 1
ParametersMax = 2
DataSets = [
- ('stepq(<stepper>)', 'Commanded position of the given stepper'),
- ('stepq(<stepper>,<time>)', 'Commanded position with smooth time'),
+ ("stepq(<stepper>)", "Commanded position of the given stepper"),
+ ("stepq(<stepper>,<time>)", "Commanded position with smooth time"),
]
+
def __init__(self, lmanager, name, name_parts):
self.name = name
self.stepper_name = name_parts[1]
self.jdispatch = lmanager.get_jdispatch()
- self.step_data = [(0., 0., 0.), (0., 0., 0.)] # [(time, half_pos, pos)]
+ self.step_data = [(0.0, 0.0, 0.0), (0.0, 0.0, 0.0)] # [(time, half_pos, pos)]
self.data_pos = 0
self.smooth_time = 0.010
if len(name_parts) == 3:
@@ -160,9 +187,11 @@ class HandleStepQ:
self.smooth_time = float(name_parts[2])
except ValueError:
raise error("Invalid stepq smooth time '%s'" % (name_parts[2],))
+
def get_label(self):
- label = '%s position' % (self.stepper_name,)
- return {'label': label, 'units': 'Position\n(mm)'}
+ label = "%s position" % (self.stepper_name,)
+ return {"label": label, "units": "Position\n(mm)"}
+
def pull_data(self, req_time):
smooth_time = self.smooth_time
while 1:
@@ -183,7 +212,7 @@ class HandleStepQ:
if stime <= smooth_time:
pdiff = next_halfpos - last_halfpos
return last_halfpos + rtdiff * pdiff / stime
- stime = .5 * smooth_time
+ stime = 0.5 * smooth_time
if rtdiff < stime:
pdiff = last_pos - last_halfpos
return last_halfpos + rtdiff * pdiff / stime
@@ -192,6 +221,7 @@ class HandleStepQ:
pdiff = last_pos - next_halfpos
return next_halfpos + rtdiff * pdiff / stime
return last_pos
+
def _pull_block(self, req_time):
step_data = self.step_data
del step_data[:-1]
@@ -201,25 +231,25 @@ class HandleStepQ:
jmsg = self.jdispatch.pull_msg(req_time, self.name)
if jmsg is None:
last_time, last_halfpos, last_pos = step_data[0]
- self.step_data.append((req_time + .1, last_pos, last_pos))
+ self.step_data.append((req_time + 0.1, last_pos, last_pos))
return
- last_time = jmsg['last_step_time']
+ last_time = jmsg["last_step_time"]
if req_time <= last_time:
break
# Process block into (time, half_position, position) 3-tuples
- first_time = step_time = jmsg['first_step_time']
- first_clock = jmsg['first_clock']
- step_clock = first_clock - jmsg['data'][0][0]
- cdiff = jmsg['last_clock'] - first_clock
+ first_time = step_time = jmsg["first_step_time"]
+ first_clock = jmsg["first_clock"]
+ step_clock = first_clock - jmsg["data"][0][0]
+ cdiff = jmsg["last_clock"] - first_clock
tdiff = last_time - first_time
- inv_freq = 0.
+ inv_freq = 0.0
if cdiff:
inv_freq = tdiff / cdiff
- step_dist = jmsg['step_distance']
- step_pos = jmsg['start_position']
+ step_dist = jmsg["step_distance"]
+ step_pos = jmsg["start_position"]
if not step_data[0][0]:
- step_data[0] = (0., step_pos, step_pos)
- for interval, raw_count, add in jmsg['data']:
+ step_data[0] = (0.0, step_pos, step_pos)
+ for interval, raw_count, add in jmsg["data"]:
qs_dist = step_dist
count = raw_count
if count < 0:
@@ -229,22 +259,30 @@ class HandleStepQ:
step_clock += interval
interval += add
step_time = first_time + (step_clock - first_clock) * inv_freq
- step_halfpos = step_pos + .5 * qs_dist
+ step_halfpos = step_pos + 0.5 * qs_dist
step_pos += qs_dist
step_data.append((step_time, step_halfpos, step_pos))
+
+
LogHandlers["stepq"] = HandleStepQ
+
# Extract tmc current and stallguard data from the log
class HandleStallguard:
SubscriptionIdParts = 2
ParametersMin = 2
ParametersMax = 2
DataSets = [
- ('stallguard(<stepper>,sg_result)',
- 'Stallguard result of the given stepper driver'),
- ('stallguard(<stepper>,cs_actual)',
- 'Current level result of the given stepper driver'),
+ (
+ "stallguard(<stepper>,sg_result)",
+ "Stallguard result of the given stepper driver",
+ ),
+ (
+ "stallguard(<stepper>,cs_actual)",
+ "Current level result of the given stepper driver",
+ ),
]
+
def __init__(self, lmanager, name, name_parts):
self.name = name
self.stepper_name = name_parts[1]
@@ -253,7 +291,7 @@ class HandleStallguard:
self.data = []
self.ret = None
self.driver_name = ""
- for k in lmanager.get_initial_status()['configfile']['settings']:
+ for k in lmanager.get_initial_status()["configfile"]["settings"]:
if not k.startswith("tmc"):
continue
if k.endswith(self.stepper_name):
@@ -261,15 +299,16 @@ class HandleStallguard:
break
# Current decode
self.status_tracker = lmanager.get_status_tracker()
- self.next_status_time = 0.
+ self.next_status_time = 0.0
self.irun = 0
+
def get_label(self):
- label = '%s %s %s' % (self.driver_name, self.stepper_name,
- self.filter)
+ label = "%s %s %s" % (self.driver_name, self.stepper_name, self.filter)
if self.filter == "sg_result":
- return {'label': label, 'units': 'Stallguard'}
+ return {"label": label, "units": "Stallguard"}
elif self.filter == "cs_actual":
- return {'label': label, 'units': 'CS Actual'}
+ return {"label": label, "units": "CS Actual"}
+
# Search datapoint in dataset extrapolate in between
def pull_data(self, req_time):
while 1:
@@ -290,25 +329,30 @@ class HandleStallguard:
if req_time <= time:
return self.ret[self.filter]
self.ret = None
+
+
LogHandlers["stallguard"] = HandleStallguard
+
# Extract stepper motor phase position
class HandleStepPhase:
SubscriptionIdParts = 0
ParametersMin = 1
ParametersMax = 2
DataSets = [
- ('step_phase(<driver>)', 'Stepper motor phase of the given stepper'),
- ('step_phase(<driver>,microstep)', 'Microstep position for stepper'),
+ ("step_phase(<driver>)", "Stepper motor phase of the given stepper"),
+ ("step_phase(<driver>,microstep)", "Microstep position for stepper"),
]
+
def __init__(self, lmanager, name, name_parts):
self.name = name
self.driver_name = name_parts[1]
self.stepper_name = " ".join(self.driver_name.split()[1:])
- config = lmanager.get_initial_status()['configfile']['settings']
+ config = lmanager.get_initial_status()["configfile"]["settings"]
if self.driver_name not in config or self.stepper_name not in config:
- raise error("Unable to find stepper driver '%s' config"
- % (self.driver_name,))
+ raise error(
+ "Unable to find stepper driver '%s' config" % (self.driver_name,)
+ )
if len(name_parts) == 3 and name_parts[2] != "microstep":
raise error("Unknown step_phase selection '%s'" % (name_parts[2],))
self.report_microsteps = len(name_parts) == 3
@@ -319,23 +363,28 @@ class HandleStepPhase:
self.jdispatch = lmanager.get_jdispatch()
self.jdispatch.add_handler(name, "stepq:" + self.stepper_name)
# stepq tracking
- self.step_data = [(0., 0), (0., 0)] # [(time, mcu_pos)]
+ self.step_data = [(0.0, 0), (0.0, 0)] # [(time, mcu_pos)]
self.data_pos = 0
# driver phase tracking
self.status_tracker = lmanager.get_status_tracker()
- self.next_status_time = 0.
+ self.next_status_time = 0.0
self.mcu_phase_offset = 0
+
def get_label(self):
if self.report_microsteps:
- return {'label': '%s microstep' % (self.stepper_name,),
- 'units': 'Microstep'}
- return {'label': '%s phase' % (self.stepper_name,), 'units': 'Phase'}
+ return {
+ "label": "%s microstep" % (self.stepper_name,),
+ "units": "Microstep",
+ }
+ return {"label": "%s phase" % (self.stepper_name,), "units": "Phase"}
+
def _pull_phase_offset(self, req_time):
db, self.next_status_time = self.status_tracker.pull_status(req_time)
- mcu_phase_offset = db.get(self.driver_name, {}).get('mcu_phase_offset')
+ mcu_phase_offset = db.get(self.driver_name, {}).get("mcu_phase_offset")
if mcu_phase_offset is None:
mcu_phase_offset = 0
self.mcu_phase_offset = mcu_phase_offset
+
def pull_data(self, req_time):
if req_time >= self.next_status_time:
self._pull_phase_offset(req_time)
@@ -352,6 +401,7 @@ class HandleStepPhase:
continue
step_pos = step_data[data_pos][1]
return (step_pos + self.mcu_phase_offset) % self.phases
+
def _pull_block(self, req_time):
step_data = self.step_data
del step_data[:-1]
@@ -361,24 +411,24 @@ class HandleStepPhase:
jmsg = self.jdispatch.pull_msg(req_time, self.name)
if jmsg is None:
last_time, last_pos = step_data[0]
- self.step_data.append((req_time + .1, last_pos))
+ self.step_data.append((req_time + 0.1, last_pos))
return
- last_time = jmsg['last_step_time']
+ last_time = jmsg["last_step_time"]
if req_time <= last_time:
break
# Process block into (time, position) 2-tuples
- first_time = step_time = jmsg['first_step_time']
- first_clock = jmsg['first_clock']
- step_clock = first_clock - jmsg['data'][0][0]
- cdiff = jmsg['last_clock'] - first_clock
+ first_time = step_time = jmsg["first_step_time"]
+ first_clock = jmsg["first_clock"]
+ step_clock = first_clock - jmsg["data"][0][0]
+ cdiff = jmsg["last_clock"] - first_clock
tdiff = last_time - first_time
- inv_freq = 0.
+ inv_freq = 0.0
if cdiff:
inv_freq = tdiff / cdiff
- step_pos = jmsg['start_mcu_position']
+ step_pos = jmsg["start_mcu_position"]
if not step_data[0][0]:
- step_data[0] = (0., step_pos)
- for interval, raw_count, add in jmsg['data']:
+ step_data[0] = (0.0, step_pos)
+ for interval, raw_count, add in jmsg["data"]:
qs_dist = 1
count = raw_count
if count < 0:
@@ -390,29 +440,35 @@ class HandleStepPhase:
step_time = first_time + (step_clock - first_clock) * inv_freq
step_pos += qs_dist
step_data.append((step_time, step_pos))
+
+
LogHandlers["step_phase"] = HandleStepPhase
+
# Extract accelerometer data
class HandleADXL345:
SubscriptionIdParts = 2
ParametersMin = ParametersMax = 2
DataSets = [
- ('adxl345(<name>,<axis>)', 'Accelerometer for given axis (x, y, or z)'),
+ ("adxl345(<name>,<axis>)", "Accelerometer for given axis (x, y, or z)"),
]
+
def __init__(self, lmanager, name, name_parts):
self.name = name
self.adxl_name = name_parts[1]
self.jdispatch = lmanager.get_jdispatch()
- self.next_accel_time = self.last_accel_time = 0.
- self.next_accel = self.last_accel = (0., 0., 0.)
+ self.next_accel_time = self.last_accel_time = 0.0
+ self.next_accel = self.last_accel = (0.0, 0.0, 0.0)
self.cur_data = []
self.data_pos = 0
- if name_parts[2] not in 'xyz':
+ if name_parts[2] not in "xyz":
raise error("Unknown adxl345 data selection '%s'" % (name,))
- self.axis = 'xyz'.index(name_parts[2])
+ self.axis = "xyz".index(name_parts[2])
+
def get_label(self):
- label = '%s %s acceleration' % (self.adxl_name, 'xyz'[self.axis])
- return {'label': label, 'units': 'Acceleration\n(mm/s^2)'}
+ label = "%s %s acceleration" % (self.adxl_name, "xyz"[self.axis])
+ return {"label": label, "units": "Acceleration\n(mm/s^2)"}
+
def pull_data(self, req_time):
axis = self.axis
while 1:
@@ -425,8 +481,8 @@ class HandleADXL345:
# Read next data block
jmsg = self.jdispatch.pull_msg(req_time, self.name)
if jmsg is None:
- return 0.
- self.cur_data = jmsg['data']
+ return 0.0
+ self.cur_data = jmsg["data"]
self.data_pos = 0
continue
self.last_accel = self.next_accel
@@ -434,42 +490,50 @@ class HandleADXL345:
self.next_accel_time, x, y, z = self.cur_data[self.data_pos]
self.next_accel = (x, y, z)
self.data_pos += 1
+
+
LogHandlers["adxl345"] = HandleADXL345
+
# Extract positions from magnetic angle sensor
class HandleAngle:
SubscriptionIdParts = 2
ParametersMin = ParametersMax = 1
DataSets = [
- ('angle(<name>)', 'Angle sensor position'),
+ ("angle(<name>)", "Angle sensor position"),
]
+
def __init__(self, lmanager, name, name_parts):
self.name = name
self.angle_name = name_parts[1]
self.jdispatch = lmanager.get_jdispatch()
- self.next_angle_time = self.last_angle_time = 0.
- self.next_angle = self.last_angle = 0.
+ self.next_angle_time = self.last_angle_time = 0.0
+ self.next_angle = self.last_angle = 0.0
self.cur_data = []
self.data_pos = 0
- self.position_offset = 0.
- self.angle_dist = 1.
+ self.position_offset = 0.0
+ self.angle_dist = 1.0
# Determine angle distance from associated stepper's rotation_distance
- config = lmanager.get_initial_status()['configfile']['settings']
- aname = 'angle %s' % (self.angle_name,)
- stepper_name = config.get(aname, {}).get('stepper')
+ config = lmanager.get_initial_status()["configfile"]["settings"]
+ aname = "angle %s" % (self.angle_name,)
+ stepper_name = config.get(aname, {}).get("stepper")
if stepper_name is not None:
sconfig = config.get(stepper_name, {})
- rotation_distance = sconfig.get('rotation_distance', 1.)
- gear_ratio = sconfig.get('gear_ratio', ())
- if type(gear_ratio) == str: # XXX
- gear_ratio = [[float(v.strip()) for v in gr.split(':')]
- for gr in gear_ratio.split(',')]
+ rotation_distance = sconfig.get("rotation_distance", 1.0)
+ gear_ratio = sconfig.get("gear_ratio", ())
+ if type(gear_ratio) == str: # XXX
+ gear_ratio = [
+ [float(v.strip()) for v in gr.split(":")]
+ for gr in gear_ratio.split(",")
+ ]
for n, d in gear_ratio:
rotation_distance *= d / n
- self.angle_dist = rotation_distance / 65536.
+ self.angle_dist = rotation_distance / 65536.0
+
def get_label(self):
- label = '%s position' % (self.angle_name,)
- return {'label': label, 'units': 'Position\n(mm)'}
+ label = "%s position" % (self.angle_name,)
+ return {"label": label, "units": "Position\n(mm)"}
+
def pull_data(self, req_time):
while 1:
if req_time <= self.next_angle_time:
@@ -477,16 +541,14 @@ class HandleAngle:
tdiff = self.next_angle_time - self.last_angle_time
rtdiff = req_time - self.last_angle_time
po = rtdiff * pdiff / tdiff
- return ((self.last_angle + po) * self.angle_dist
- + self.position_offset)
+ return (self.last_angle + po) * self.angle_dist + self.position_offset
if self.data_pos >= len(self.cur_data):
# Read next data block
jmsg = self.jdispatch.pull_msg(req_time, self.name)
if jmsg is None:
- return (self.next_angle * self.angle_dist
- + self.position_offset)
- self.cur_data = jmsg['data']
- position_offset = jmsg.get('position_offset')
+ return self.next_angle * self.angle_dist + self.position_offset
+ self.cur_data = jmsg["data"]
+ position_offset = jmsg.get("position_offset")
if position_offset is not None:
self.position_offset = position_offset
self.data_pos = 0
@@ -495,24 +557,29 @@ class HandleAngle:
self.last_angle_time = self.next_angle_time
self.next_angle_time, self.next_angle = self.cur_data[self.data_pos]
self.data_pos += 1
+
+
LogHandlers["angle"] = HandleAngle
+
def interpolate(next_val, prev_val, next_time, prev_time, req_time):
vdiff = next_val - prev_val
tdiff = next_time - prev_time
rtdiff = req_time - prev_time
return prev_val + rtdiff * vdiff / tdiff
+
# Extract eddy current data
class HandleEddyCurrent:
SubscriptionIdParts = 2
ParametersMin = 1
ParametersMax = 2
DataSets = [
- ('ldc1612(<name>)', 'Coil resonant frequency'),
- ('ldc1612(<name>,period)', 'Coil resonant period'),
- ('ldc1612(<name>,z)', 'Estimated Z height'),
+ ("ldc1612(<name>)", "Coil resonant frequency"),
+ ("ldc1612(<name>,period)", "Coil resonant period"),
+ ("ldc1612(<name>,z)", "Estimated Z height"),
]
+
def __init__(self, lmanager, name, name_parts):
self.name = name
self.sensor_name = name_parts[1]
@@ -521,18 +588,20 @@ class HandleEddyCurrent:
self.report_frequency = len(name_parts) == 2
self.report_z = len(name_parts) == 3 and name_parts[2] == "z"
self.jdispatch = lmanager.get_jdispatch()
- self.next_samp = self.prev_samp = [0., 0., 0.]
+ self.next_samp = self.prev_samp = [0.0, 0.0, 0.0]
self.cur_data = []
self.data_pos = 0
+
def get_label(self):
if self.report_frequency:
- label = '%s frequency' % (self.sensor_name,)
- return {'label': label, 'units': 'Frequency\n(Hz)'}
+ label = "%s frequency" % (self.sensor_name,)
+ return {"label": label, "units": "Frequency\n(Hz)"}
if self.report_z:
- label = '%s height' % (self.sensor_name,)
- return {'label': label, 'units': 'Position\n(mm)'}
- label = '%s period' % (self.sensor_name,)
- return {'label': label, 'units': 'Period\n(s)'}
+ label = "%s height" % (self.sensor_name,)
+ return {"label": label, "units": "Position\n(mm)"}
+ label = "%s period" % (self.sensor_name,)
+ return {"label": label, "units": "Period\n(s)"}
+
def pull_data(self, req_time):
while 1:
next_time, next_freq, next_z = self.next_samp
@@ -545,21 +614,22 @@ class HandleEddyCurrent:
next_val = next_z
prev_val = prev_z
else:
- next_val = 1. / next_freq
- prev_val = 1. / prev_freq
- return interpolate(next_val, prev_val, next_time, prev_time,
- req_time)
+ next_val = 1.0 / next_freq
+ prev_val = 1.0 / prev_freq
+ return interpolate(next_val, prev_val, next_time, prev_time, req_time)
if self.data_pos >= len(self.cur_data):
# Read next data block
jmsg = self.jdispatch.pull_msg(req_time, self.name)
if jmsg is None:
- return 0.
- self.cur_data = jmsg['data']
+ return 0.0
+ self.cur_data = jmsg["data"]
self.data_pos = 0
continue
self.prev_samp = self.next_samp
self.next_samp = self.cur_data[self.data_pos]
self.data_pos += 1
+
+
LogHandlers["ldc1612"] = HandleEddyCurrent
@@ -567,15 +637,18 @@ LogHandlers["ldc1612"] = HandleEddyCurrent
# Log reading
######################################################################
+
# Read, uncompress, and parse messages in a log built by data_logger.py
class JsonLogReader:
def __init__(self, filename):
self.file = open(filename, "rb")
self.comp = zlib.decompressobj(31)
self.msgs = [b""]
+
def seek(self, pos):
self.file.seek(pos)
self.comp = zlib.decompressobj(-15)
+
def pull_msg(self):
msgs = self.msgs
while 1:
@@ -591,55 +664,61 @@ class JsonLogReader:
if not raw_data:
return None
data = self.comp.decompress(raw_data)
- parts = data.split(b'\x03')
+ parts = data.split(b"\x03")
parts[0] = msgs[0] + parts[0]
self.msgs = msgs = parts
+
# Store messages in per-subscription queues until handlers are ready for them
class JsonDispatcher:
def __init__(self, log_prefix):
self.names = {}
self.queues = {}
- self.last_read_time = 0.
+ self.last_read_time = 0.0
self.log_reader = JsonLogReader(log_prefix + ".json.gz")
self.is_eof = False
+
def check_end_of_data(self):
return self.is_eof and not any(self.queues.values())
+
def add_handler(self, name, subscription_id):
self.names[name] = q = []
self.queues.setdefault(subscription_id, []).append(q)
+
def pull_msg(self, req_time, name):
q = self.names[name]
while 1:
if q:
return q.pop(0)
- if req_time + 1. < self.last_read_time:
+ if req_time + 1.0 < self.last_read_time:
return None
json_msg = self.log_reader.pull_msg()
if json_msg is None:
self.is_eof = True
return None
- qid = json_msg.get('q')
- if qid == 'status':
- pt = json_msg.get('toolhead', {}).get('estimated_print_time')
+ qid = json_msg.get("q")
+ if qid == "status":
+ pt = json_msg.get("toolhead", {}).get("estimated_print_time")
if pt is not None:
self.last_read_time = pt
for mq in self.queues.get(qid, []):
- mq.append(json_msg['params'])
+ mq.append(json_msg["params"])
######################################################################
# Dataset and log tracking
######################################################################
+
# Tracking of get_status messages
class TrackStatus:
def __init__(self, lmanager, name, start_status):
self.name = name
self.jdispatch = lmanager.get_jdispatch()
- self.next_status_time = 0.
+ self.next_status_time = 0.0
self.status = dict(start_status)
self.next_update = {}
+
def pull_status(self, req_time):
status = self.status
while 1:
@@ -652,32 +731,35 @@ class TrackStatus:
self.next_status_time = req_time + 0.100
self.next_update = {}
return status, self.next_status_time
- self.next_update = jmsg['status']
- th = self.next_update.get('toolhead', {})
- self.next_status_time = th.get('estimated_print_time', 0.)
+ self.next_update = jmsg["status"]
+ th = self.next_update.get("toolhead", {})
+ self.next_status_time = th.get("estimated_print_time", 0.0)
+
# Split a string by commas while keeping parenthesis intact
def param_split(line):
out = []
level = prev = 0
for i, c in enumerate(line):
- if not level and c == ',':
+ if not level and c == ",":
out.append(line[prev:i])
- prev = i+1
- elif c == '(':
+ prev = i + 1
+ elif c == "(":
level += 1
- elif level and c== ')':
+ elif level and c == ")":
level -= 1
out.append(line[prev:])
return out
+
# Split a dataset name (eg, "abc(def,ghi)") into parts
def name_split(name):
- if '(' not in name or not name.endswith(')'):
+ if "(" not in name or not name.endswith(")"):
raise error("Malformed dataset name '%s'" % (name,))
- aname, aparams = name.split('(', 1)
+ aname, aparams = name.split("(", 1)
return [aname] + param_split(aparams[:-1])
+
# Return a description of possible datasets
def list_datasets():
datasets = []
@@ -685,58 +767,69 @@ def list_datasets():
datasets += LogHandlers[lh].DataSets
return datasets
+
# Main log access management
class LogManager:
error = error
+
def __init__(self, log_prefix):
self.index_reader = JsonLogReader(log_prefix + ".index.gz")
self.jdispatch = JsonDispatcher(log_prefix)
- self.initial_start_time = self.start_time = 0.
+ self.initial_start_time = self.start_time = 0.0
self.datasets = {}
self.initial_status = {}
self.start_status = {}
self.log_subscriptions = {}
self.status_tracker = None
+
def setup_index(self):
fmsg = self.index_reader.pull_msg()
- self.initial_status = status = fmsg['status']
+ self.initial_status = status = fmsg["status"]
self.start_status = dict(status)
- start_time = status['toolhead']['estimated_print_time']
+ start_time = status["toolhead"]["estimated_print_time"]
self.initial_start_time = self.start_time = start_time
- self.log_subscriptions = fmsg.get('subscriptions', {})
+ self.log_subscriptions = fmsg.get("subscriptions", {})
+
def get_initial_status(self):
return self.initial_status
+
def available_dataset_types(self):
return {name: None for name in LogHandlers}
+
def get_jdispatch(self):
return self.jdispatch
+
def seek_time(self, req_time):
self.start_time = req_start_time = self.initial_start_time + req_time
start_status = self.start_status
- seek_time = max(self.initial_start_time, req_start_time - 1.)
+ seek_time = max(self.initial_start_time, req_start_time - 1.0)
file_position = 0
while 1:
fmsg = self.index_reader.pull_msg()
if fmsg is None:
break
- th = fmsg['status']['toolhead']
- ptime = max(th['estimated_print_time'], th.get('print_time', 0.))
+ th = fmsg["status"]["toolhead"]
+ ptime = max(th["estimated_print_time"], th.get("print_time", 0.0))
if ptime > seek_time:
break
for k, v in fmsg["status"].items():
start_status.setdefault(k, {}).update(v)
- file_position = fmsg['file_position']
+ file_position = fmsg["file_position"]
if file_position:
self.jdispatch.log_reader.seek(file_position)
+
def get_initial_start_time(self):
return self.initial_start_time
+
def get_start_time(self):
return self.start_time
+
def get_status_tracker(self):
if self.status_tracker is None:
self.status_tracker = TrackStatus(self, "status", self.start_status)
self.jdispatch.add_handler("status", "status")
return self.status_tracker
+
def setup_dataset(self, name):
if name in self.datasets:
return self.datasets[name]
@@ -748,7 +841,7 @@ class LogManager:
if len_pp < cls.ParametersMin or len_pp > cls.ParametersMax:
raise error("Invalid number of parameters for '%s'" % (name,))
if cls.SubscriptionIdParts:
- subscription_id = ":".join(name_parts[:cls.SubscriptionIdParts])
+ subscription_id = ":".join(name_parts[: cls.SubscriptionIdParts])
if subscription_id not in self.log_subscriptions:
raise error("Dataset '%s' not in capture" % (subscription_id,))
self.jdispatch.add_handler(name, subscription_id)
diff --git a/scripts/parsecandump.py b/scripts/parsecandump.py
index d575dc5a..4ae862dc 100755
--- a/scripts/parsecandump.py
+++ b/scripts/parsecandump.py
@@ -6,26 +6,31 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, optparse
+
def import_msgproto():
global msgproto
# Load msgproto.py module
- kdir = os.path.join(os.path.dirname(__file__), '..', 'klippy')
+ kdir = os.path.join(os.path.dirname(__file__), "..", "klippy")
sys.path.append(kdir)
import msgproto
+
def read_dictionary(filename):
- dfile = open(filename, 'rb')
+ dfile = open(filename, "rb")
dictionary = dfile.read()
dfile.close()
return dictionary
+
def report(msg, line_info, name="", is_err=False):
line_number, line_time = line_info
warn = ""
if is_err:
warn = " WARN"
- sys.stdout.write("%04d:%010.6f:%s%s %s\n"
- % (line_number, line_time, name, warn, msg))
+ sys.stdout.write(
+ "%04d:%010.6f:%s%s %s\n" % (line_number, line_time, name, warn, msg)
+ )
+
class canscan:
def __init__(self, name, mp):
@@ -33,6 +38,7 @@ class canscan:
self.mp = mp
self.data = bytearray()
self.need_scan = False
+
def handle_data(self, line_info, line, newdata):
data = self.data
data += bytearray(newdata)
@@ -44,8 +50,12 @@ class canscan:
drop = syncpos + 1
self.need_scan = False
disc = ["%02X" % (d,) for d in data[:drop]]
- report("Discarding %d (%s)" % (drop, " ".join(disc)),
- line_info, self.name, is_err=True)
+ report(
+ "Discarding %d (%s)" % (drop, " ".join(disc)),
+ line_info,
+ self.name,
+ is_err=True,
+ )
data[:drop] = []
if not data:
break
@@ -53,17 +63,22 @@ class canscan:
if l == 0:
break
if l < 0:
- report("Invalid data: %s" % (line.strip(),),
- line_info, self.name, is_err=True)
+ report(
+ "Invalid data: %s" % (line.strip(),),
+ line_info,
+ self.name,
+ is_err=True,
+ )
self.need_scan = True
continue
if l == 5:
report("Ack %02x" % (data[1],), line_info, self.name)
else:
msgs = self.mp.dump(data[:l])
- report("%d: %s" % (l, ', '.join(msgs)), line_info, self.name)
+ report("%d: %s" % (l, ", ".join(msgs)), line_info, self.name)
data[:l] = []
+
def read_candump(canfile, canid, dictionary):
mp = msgproto.MessageParser()
mp.process_identify(dictionary, decompress=False)
@@ -71,7 +86,7 @@ def read_candump(canfile, canid, dictionary):
txid = "%03X" % (canid & ~1,)
handlers = {rxid: canscan("RX", mp), txid: canscan("TX", mp)}
- last_time = -1.
+ last_time = -1.0
line_number = 0
must_scan = False
data = bytearray()
@@ -80,24 +95,34 @@ def read_candump(canfile, canid, dictionary):
parts = line.split()
if len(parts) < 7:
if parts:
- report("Ignoring line: %s" % (line.strip(),),
- (line_number, 0.), is_err=True)
+ report(
+ "Ignoring line: %s" % (line.strip(),),
+ (line_number, 0.0),
+ is_err=True,
+ )
continue
p_ts = parts[0]
p_canid = parts[5]
p_len = parts[6]
p_data = parts[7:]
- if (not p_ts.startswith('(') or not p_ts.endswith(')')
- or not p_len.startswith('[') or not p_len.endswith(']')):
- report("Ignoring line: %s" % (line.strip(),),
- (line_number, 0.), is_err=True)
+ if (
+ not p_ts.startswith("(")
+ or not p_ts.endswith(")")
+ or not p_len.startswith("[")
+ or not p_len.endswith("]")
+ ):
+ report(
+ "Ignoring line: %s" % (line.strip(),), (line_number, 0.0), is_err=True
+ )
continue
new_time = float(p_ts[1:-1])
line_info = (line_number, new_time)
if new_time < last_time:
- report("Backwards time %.6f vs %.6f: %s"
- % (new_time, last_time, line.strip()),
- line_info, is_err=True)
+ report(
+ "Backwards time %.6f vs %.6f: %s" % (new_time, last_time, line.strip()),
+ line_info,
+ is_err=True,
+ )
last_time = new_time
hdlr = handlers.get(p_canid)
@@ -105,6 +130,7 @@ def read_candump(canfile, canid, dictionary):
newdata = [int(i, 16) for i in p_data]
hdlr.handle_data(line_info, line, newdata)
+
def main():
usage = "%prog <candump.log> <canid> <mcu.dict>"
opts = optparse.OptionParser(usage)
@@ -121,5 +147,6 @@ def main():
read_candump(canfile, canid, dictionary)
canfile.close()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py
index 44eefa4f..e6e50a72 100644
--- a/scripts/spi_flash/board_defs.py
+++ b/scripts/spi_flash/board_defs.py
@@ -11,165 +11,125 @@
###########################################################
BOARD_DEFS = {
- 'generic-lpc1768': {
- 'mcu': "lpc1768",
- 'spi_bus': "ssp1",
- "cs_pin": "P0.6"
- },
- 'generic-lpc1769': {
- 'mcu': "lpc1769",
- 'spi_bus': "ssp1",
- "cs_pin": "P0.6"
- },
- 'btt-skr-mini': {
- 'mcu': "stm32f103xe",
- 'spi_bus': "spi1",
- "cs_pin": "PA4"
- },
- 'btt-skr-mini-v3': {
- 'mcu': "stm32g0b1xx",
- 'spi_bus': "spi1",
- "cs_pin": "PA4"
- },
- 'btt-skr-mini-v3-b0': {
- 'mcu': "stm32g0b0xx",
- 'spi_bus': "spi1",
- "cs_pin": "PA4"
- },
- 'flyboard-mini': {
- 'mcu': "stm32f103xe",
- 'spi_bus': "spi2",
+ "generic-lpc1768": {"mcu": "lpc1768", "spi_bus": "ssp1", "cs_pin": "P0.6"},
+ "generic-lpc1769": {"mcu": "lpc1769", "spi_bus": "ssp1", "cs_pin": "P0.6"},
+ "btt-skr-mini": {"mcu": "stm32f103xe", "spi_bus": "spi1", "cs_pin": "PA4"},
+ "btt-skr-mini-v3": {"mcu": "stm32g0b1xx", "spi_bus": "spi1", "cs_pin": "PA4"},
+ "btt-skr-mini-v3-b0": {"mcu": "stm32g0b0xx", "spi_bus": "spi1", "cs_pin": "PA4"},
+ "flyboard-mini": {
+ "mcu": "stm32f103xe",
+ "spi_bus": "spi2",
"cs_pin": "PB12",
- "current_firmware_path": "FLY.CUR"
+ "current_firmware_path": "FLY.CUR",
},
- 'mks-robin-e3': {
- 'mcu': "stm32f103xe",
- 'spi_bus': "spi2",
+ "mks-robin-e3": {
+ "mcu": "stm32f103xe",
+ "spi_bus": "spi2",
"cs_pin": "PA15",
"conversion_script": "scripts/update_mks_robin.py",
"firmware_path": "Robin_e3.bin",
- "current_firmware_path": "Robin_e3.cur"
+ "current_firmware_path": "Robin_e3.cur",
},
# twotrees sapphire 5 v1.1 using mks robin nano 1.2 board
- 'mks-robin-v12': {
- 'mcu': "stm32f103xe",
- 'spi_bus': "swspi",
- 'spi_pins': "PC8,PD2,PC12",
- 'cs_pin': "PC11",
- 'skip_verify': True,
+ "mks-robin-v12": {
+ "mcu": "stm32f103xe",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
+ "cs_pin": "PC11",
+ "skip_verify": True,
"conversion_script": "scripts/update_mks_robin.py",
"firmware_path": "ROBIN_NANO35.BIN",
- "current_firmware_path": "ROBIN_NANO35.BIN"
+ "current_firmware_path": "ROBIN_NANO35.BIN",
},
- 'btt-octopus-f407-v1': {
- 'mcu': "stm32f407xx",
- 'spi_bus': "swspi",
- 'spi_pins': "PC8,PD2,PC12",
- 'cs_pin': "PC11",
- 'skip_verify': True
- },
- 'btt-octopus-f429-v1': {
- 'mcu': "stm32f429xx",
- 'spi_bus': "swspi",
- 'spi_pins': "PC8,PD2,PC12",
- 'cs_pin': "PC11",
- 'skip_verify': True
- },
- 'btt-octopus-f446-v1': {
- 'mcu': "stm32f446xx",
- 'spi_bus': "swspi",
- 'spi_pins': "PC8,PD2,PC12",
- 'cs_pin': "PC11",
- 'skip_verify': True
+ "btt-octopus-f407-v1": {
+ "mcu": "stm32f407xx",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
+ "cs_pin": "PC11",
+ "skip_verify": True,
},
- 'btt-skr-pro': {
- 'mcu': "stm32f407xx",
- 'spi_bus': "swspi",
- 'spi_pins': "PA6,PB5,PA5",
- "cs_pin": "PA4"
+ "btt-octopus-f429-v1": {
+ "mcu": "stm32f429xx",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
+ "cs_pin": "PC11",
+ "skip_verify": True,
},
- 'btt-gtr': {
- 'mcu': "stm32f407xx",
- 'spi_bus': "spi1",
- "cs_pin": "PA4"
+ "btt-octopus-f446-v1": {
+ "mcu": "stm32f446xx",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
+ "cs_pin": "PC11",
+ "skip_verify": True,
},
- 'fysetc-spider': {
- 'mcu': "stm32f446xx",
- 'spi_bus': "spi1",
+ "btt-skr-pro": {
+ "mcu": "stm32f407xx",
+ "spi_bus": "swspi",
+ "spi_pins": "PA6,PB5,PA5",
"cs_pin": "PA4",
- "current_firmware_path": "OLD.BIN"
},
- 'btt-skr-se-bx': {
- 'mcu': 'stm32h743xx',
- 'spi_bus': 'spi3a',
- 'cs_pin': 'PA15'
- },
- 'btt-skr-3-h743': {
- 'mcu': 'stm32h743xx',
- 'spi_bus': 'swspi',
- 'spi_pins': "PC8,PD2,PC12",
- 'cs_pin': 'PC11',
- 'skip_verify': True
- },
- 'btt-skr-3-h723': {
- 'mcu': 'stm32h723xx',
- 'spi_bus': 'swspi',
- 'spi_pins': "PC8,PD2,PC12",
- 'cs_pin': 'PC11',
- 'skip_verify': True
+ "btt-gtr": {"mcu": "stm32f407xx", "spi_bus": "spi1", "cs_pin": "PA4"},
+ "fysetc-spider": {
+ "mcu": "stm32f446xx",
+ "spi_bus": "spi1",
+ "cs_pin": "PA4",
+ "current_firmware_path": "OLD.BIN",
},
- 'creality-v4.2.2': {
- 'mcu': "stm32f103xe",
- 'spi_bus': "swspi",
- 'spi_pins': "PC8,PD2,PC12",
- 'cs_pin': "PC11",
- 'skip_verify': True
+ "btt-skr-se-bx": {"mcu": "stm32h743xx", "spi_bus": "spi3a", "cs_pin": "PA15"},
+ "btt-skr-3-h743": {
+ "mcu": "stm32h743xx",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
+ "cs_pin": "PC11",
+ "skip_verify": True,
},
- 'monster8': {
- 'mcu': "stm32f407xx",
- 'spi_bus': "spi3a",
- "cs_pin": "PC9"
+ "btt-skr-3-h723": {
+ "mcu": "stm32h723xx",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
+ "cs_pin": "PC11",
+ "skip_verify": True,
},
- 'fly-gemini-v2': {
- 'mcu': "stm32f405xx",
- 'spi_bus': "spi1",
- "cs_pin": "PA4"
+ "creality-v4.2.2": {
+ "mcu": "stm32f103xe",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
+ "cs_pin": "PC11",
+ "skip_verify": True,
},
- 'fysetc-cheetah': {
- 'mcu': "stm32f401xc",
- 'spi_bus': "spi1",
+ "monster8": {"mcu": "stm32f407xx", "spi_bus": "spi3a", "cs_pin": "PC9"},
+ "fly-gemini-v2": {"mcu": "stm32f405xx", "spi_bus": "spi1", "cs_pin": "PA4"},
+ "fysetc-cheetah": {
+ "mcu": "stm32f401xc",
+ "spi_bus": "spi1",
"cs_pin": "PA4",
- "current_firmware_path": "OLD.BIN"
+ "current_firmware_path": "OLD.BIN",
},
- 'btt-octopus-max-ez': {
- 'mcu': "stm32h723xx",
- 'spi_bus': "swspi",
- 'spi_pins': "PE13,PE14,PE12",
- 'cs_pin': "PB12",
- 'skip_verify': True
- },
- 'btt-skrat': {
- 'mcu': "stm32g0b1xx",
- 'spi_bus': "spi1",
- "cs_pin": "PB8"
+ "btt-octopus-max-ez": {
+ "mcu": "stm32h723xx",
+ "spi_bus": "swspi",
+ "spi_pins": "PE13,PE14,PE12",
+ "cs_pin": "PB12",
+ "skip_verify": True,
},
- 'chitu-v6': {
- 'mcu': "stm32f103xe",
- 'spi_bus': "swspi",
- 'spi_pins': "PC8,PD2,PC12",
+ "btt-skrat": {"mcu": "stm32g0b1xx", "spi_bus": "spi1", "cs_pin": "PB8"},
+ "chitu-v6": {
+ "mcu": "stm32f103xe",
+ "spi_bus": "swspi",
+ "spi_pins": "PC8,PD2,PC12",
"cs_pin": "PC11",
#'sdio_bus': 'sdio',
"conversion_script": "scripts/update_chitu.py",
"firmware_path": "update.cbd",
- 'skip_verify': True
+ "skip_verify": True,
},
- 'znp-robin-nano-dw-v2.2': {
- 'mcu': "stm32f401xc",
- 'spi_bus': "spi2",
+ "znp-robin-nano-dw-v2.2": {
+ "mcu": "stm32f401xc",
+ "spi_bus": "spi2",
"cs_pin": "PB12",
"firmware_path": "ZNP_ROBIN_NANO.bin",
- "current_firmware_path": "ZNP_ROBIN_NANO.CUR"
- }
+ "current_firmware_path": "ZNP_ROBIN_NANO.CUR",
+ },
}
###########################################################
@@ -179,52 +139,54 @@ BOARD_DEFS = {
###########################################################
BOARD_ALIASES = {
- 'btt-skr-v1.1': BOARD_DEFS['generic-lpc1768'],
- 'btt-skr-v1.3': BOARD_DEFS['generic-lpc1768'],
- 'btt-skr-v1.4': BOARD_DEFS['generic-lpc1768'],
- 'mks-sgenl-v1': BOARD_DEFS['generic-lpc1768'],
- 'mks-sbase': BOARD_DEFS['generic-lpc1768'],
- 'smoothieboard-v1': BOARD_DEFS['generic-lpc1769'],
- 'btt-skr-turbo-v1.4': BOARD_DEFS['generic-lpc1769'],
- 'btt-skr-e3-turbo': BOARD_DEFS['generic-lpc1769'],
- 'mks-sgenl-v2': BOARD_DEFS['generic-lpc1769'],
- 'btt-skr-mini-v1.1': BOARD_DEFS['btt-skr-mini'],
- 'btt-skr-mini-e3-v1': BOARD_DEFS['btt-skr-mini'],
- 'btt-skr-mini-e3-v1.2': BOARD_DEFS['btt-skr-mini'],
- 'btt-skr-mini-e3-v2': BOARD_DEFS['btt-skr-mini'],
- 'btt-skr-mini-e3-v3': BOARD_DEFS['btt-skr-mini-v3'],
- 'btt-skr-mini-e3-v3-b0': BOARD_DEFS['btt-skr-mini-v3-b0'],
- 'btt-skr-mini-mz': BOARD_DEFS['btt-skr-mini'],
- 'btt-skr-e3-dip': BOARD_DEFS['btt-skr-mini'],
- 'btt002-v1': BOARD_DEFS['btt-skr-mini'],
- 'creality-v4.2.7': BOARD_DEFS['creality-v4.2.2'],
- 'btt-skr-2-f407': BOARD_DEFS['btt-octopus-f407-v1'],
- 'btt-skr-2-f429': BOARD_DEFS['btt-octopus-f429-v1'],
- 'btt-octopus-f407-v1.0': BOARD_DEFS['btt-octopus-f407-v1'],
- 'btt-octopus-f407-v1.1': BOARD_DEFS['btt-octopus-f407-v1'],
- 'btt-octopus-f429-v1.0': BOARD_DEFS['btt-octopus-f429-v1'],
- 'btt-octopus-f429-v1.1': BOARD_DEFS['btt-octopus-f429-v1'],
- 'btt-octopus-f446-v1.0': BOARD_DEFS['btt-octopus-f446-v1'],
- 'btt-octopus-f446-v1.1': BOARD_DEFS['btt-octopus-f446-v1'],
- 'btt-octopus-pro-f429-v1.0': BOARD_DEFS['btt-octopus-f429-v1'],
- 'btt-octopus-pro-f446-v1.0': BOARD_DEFS['btt-octopus-f446-v1'],
- 'btt-octopus-pro-h723-v1.1': BOARD_DEFS['btt-skr-3-h723'],
- 'btt-skr-pro-v1.1': BOARD_DEFS['btt-skr-pro'],
- 'btt-skr-pro-v1.2': BOARD_DEFS['btt-skr-pro'],
- 'btt-gtr-v1': BOARD_DEFS['btt-gtr'],
- 'mks-robin-e3d': BOARD_DEFS['mks-robin-e3'],
- 'fysetc-cheetah-v2': BOARD_DEFS['fysetc-cheetah'],
- 'fysetc-spider-v1': BOARD_DEFS['fysetc-spider'],
- 'fysetc-s6-v1.2': BOARD_DEFS['fysetc-spider'],
- 'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'],
- 'robin_v3': BOARD_DEFS['monster8'],
- 'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'],
- 'chitu-v6': BOARD_DEFS['chitu-v6']
+ "btt-skr-v1.1": BOARD_DEFS["generic-lpc1768"],
+ "btt-skr-v1.3": BOARD_DEFS["generic-lpc1768"],
+ "btt-skr-v1.4": BOARD_DEFS["generic-lpc1768"],
+ "mks-sgenl-v1": BOARD_DEFS["generic-lpc1768"],
+ "mks-sbase": BOARD_DEFS["generic-lpc1768"],
+ "smoothieboard-v1": BOARD_DEFS["generic-lpc1769"],
+ "btt-skr-turbo-v1.4": BOARD_DEFS["generic-lpc1769"],
+ "btt-skr-e3-turbo": BOARD_DEFS["generic-lpc1769"],
+ "mks-sgenl-v2": BOARD_DEFS["generic-lpc1769"],
+ "btt-skr-mini-v1.1": BOARD_DEFS["btt-skr-mini"],
+ "btt-skr-mini-e3-v1": BOARD_DEFS["btt-skr-mini"],
+ "btt-skr-mini-e3-v1.2": BOARD_DEFS["btt-skr-mini"],
+ "btt-skr-mini-e3-v2": BOARD_DEFS["btt-skr-mini"],
+ "btt-skr-mini-e3-v3": BOARD_DEFS["btt-skr-mini-v3"],
+ "btt-skr-mini-e3-v3-b0": BOARD_DEFS["btt-skr-mini-v3-b0"],
+ "btt-skr-mini-mz": BOARD_DEFS["btt-skr-mini"],
+ "btt-skr-e3-dip": BOARD_DEFS["btt-skr-mini"],
+ "btt002-v1": BOARD_DEFS["btt-skr-mini"],
+ "creality-v4.2.7": BOARD_DEFS["creality-v4.2.2"],
+ "btt-skr-2-f407": BOARD_DEFS["btt-octopus-f407-v1"],
+ "btt-skr-2-f429": BOARD_DEFS["btt-octopus-f429-v1"],
+ "btt-octopus-f407-v1.0": BOARD_DEFS["btt-octopus-f407-v1"],
+ "btt-octopus-f407-v1.1": BOARD_DEFS["btt-octopus-f407-v1"],
+ "btt-octopus-f429-v1.0": BOARD_DEFS["btt-octopus-f429-v1"],
+ "btt-octopus-f429-v1.1": BOARD_DEFS["btt-octopus-f429-v1"],
+ "btt-octopus-f446-v1.0": BOARD_DEFS["btt-octopus-f446-v1"],
+ "btt-octopus-f446-v1.1": BOARD_DEFS["btt-octopus-f446-v1"],
+ "btt-octopus-pro-f429-v1.0": BOARD_DEFS["btt-octopus-f429-v1"],
+ "btt-octopus-pro-f446-v1.0": BOARD_DEFS["btt-octopus-f446-v1"],
+ "btt-octopus-pro-h723-v1.1": BOARD_DEFS["btt-skr-3-h723"],
+ "btt-skr-pro-v1.1": BOARD_DEFS["btt-skr-pro"],
+ "btt-skr-pro-v1.2": BOARD_DEFS["btt-skr-pro"],
+ "btt-gtr-v1": BOARD_DEFS["btt-gtr"],
+ "mks-robin-e3d": BOARD_DEFS["mks-robin-e3"],
+ "fysetc-cheetah-v2": BOARD_DEFS["fysetc-cheetah"],
+ "fysetc-spider-v1": BOARD_DEFS["fysetc-spider"],
+ "fysetc-s6-v1.2": BOARD_DEFS["fysetc-spider"],
+ "fysetc-s6-v2": BOARD_DEFS["fysetc-spider"],
+ "robin_v3": BOARD_DEFS["monster8"],
+ "btt-skrat-v1.0": BOARD_DEFS["btt-skrat"],
+ "chitu-v6": BOARD_DEFS["chitu-v6"],
}
+
def list_boards():
return sorted(list(BOARD_DEFS.keys()) + list(BOARD_ALIASES.keys()))
+
def lookup_board(name):
name = name.lower()
bdef = BOARD_ALIASES.get(name, BOARD_DEFS.get(name, None))
diff --git a/scripts/spi_flash/fatfs_lib.py b/scripts/spi_flash/fatfs_lib.py
index ef92070a..6ad37e66 100644
--- a/scripts/spi_flash/fatfs_lib.py
+++ b/scripts/spi_flash/fatfs_lib.py
@@ -5,8 +5,8 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import os
import sys
-KLIPPER_DIR = os.path.abspath(os.path.join(
- os.path.dirname(__file__), "../../"))
+
+KLIPPER_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
sys.path.append(os.path.join(KLIPPER_DIR, "klippy"))
import chelper
@@ -58,6 +58,7 @@ FATFS_CDEFS = """
fatfs_ffi_main = None
fatfs_ffi_lib = None
+
def check_fatfs_build(printfunc=lambda o: o):
printfunc("Checking FatFS CFFI Build...\n")
ffi_main, ffi_lib = chelper.get_ffi()
@@ -67,20 +68,24 @@ def check_fatfs_build(printfunc=lambda o: o):
ofiles = chelper.get_abs_files(FATFS_DIR, FATFS_HEADERS)
ofiles.extend(chelper.get_abs_files(srcdir, SPI_FLASH_HEADERS))
destlib = os.path.join(srcdir, DEST_LIB)
- if chelper.check_build_code(srcfiles+ofiles+[__file__], destlib):
+ if chelper.check_build_code(srcfiles + ofiles + [__file__], destlib):
if chelper.check_gcc_option(chelper.SSE_FLAGS):
- cmd = "%s %s %s" % (chelper.GCC_CMD, chelper.SSE_FLAGS,
- chelper.COMPILE_ARGS)
+ cmd = "%s %s %s" % (
+ chelper.GCC_CMD,
+ chelper.SSE_FLAGS,
+ chelper.COMPILE_ARGS,
+ )
else:
cmd = "%s %s" % (chelper.GCC_CMD, chelper.COMPILE_ARGS)
printfunc("Building FatFS shared library...")
- chelper.do_build_code(cmd % (destlib, ' '.join(srcfiles)))
+ chelper.do_build_code(cmd % (destlib, " ".join(srcfiles)))
printfunc("Done\n")
global fatfs_ffi_main, fatfs_ffi_lib
ffi_main.cdef(FATFS_CDEFS)
fatfs_ffi_lib = ffi_main.dlopen(destlib)
fatfs_ffi_main = ffi_main
+
def get_fatfs_ffi():
global fatfs_ffi_main, fatfs_ffi_lib
if fatfs_ffi_main is None:
diff --git a/scripts/spi_flash/spi_flash.py b/scripts/spi_flash/spi_flash.py
index 729dd2bb..f784f0a4 100644
--- a/scripts/spi_flash/spi_flash.py
+++ b/scripts/spi_flash/spi_flash.py
@@ -29,14 +29,17 @@ import mcu
#
###########################################################
+
def output_line(msg):
sys.stdout.write("%s\n" % (msg,))
sys.stdout.flush()
+
def output(msg):
sys.stdout.write("%s" % (msg,))
sys.stdout.flush()
+
def calc_crc7(data, with_padding=True):
# G(x) = x^7 + x^3 + 1
# Shift left as we are only calculating a 7 bit CRC
@@ -52,6 +55,7 @@ def calc_crc7(data, with_padding=True):
return crc
return crc | 1
+
def calc_crc16(data):
poly = 0b10001000000100001
crc = 0
@@ -61,32 +65,34 @@ def calc_crc16(data):
crc = (crc << 1) ^ poly if crc & 0x8000 else crc << 1
return crc & 0xFFFF
+
# Translate a serial device name to a stable serial name in
# /dev/serial/by-path/
# Borrowed from klipper/scripts/flash_usb.py
def translate_serial_to_tty(device):
ttyname = os.path.realpath(device)
- if os.path.exists('/dev/serial/by-path/'):
- for fname in os.listdir('/dev/serial/by-path/'):
- fname = '/dev/serial/by-path/' + fname
+ if os.path.exists("/dev/serial/by-path/"):
+ for fname in os.listdir("/dev/serial/by-path/"):
+ fname = "/dev/serial/by-path/" + fname
if os.path.realpath(fname) == ttyname:
return ttyname, fname
return ttyname, ttyname
+
def check_need_convert(board_name, config):
conv_script = config.get("conversion_script")
if conv_script is None:
return
conv_util = os.path.join(fatfs_lib.KLIPPER_DIR, conv_script)
- klipper_bin = config['klipper_bin_path']
+ klipper_bin = config["klipper_bin_path"]
dest_bin = os.path.join(
- os.path.dirname(klipper_bin),
- os.path.basename(config['firmware_path']))
+ os.path.dirname(klipper_bin), os.path.basename(config["firmware_path"])
+ )
cmd = "%s %s %s %s" % (sys.executable, conv_util, klipper_bin, dest_bin)
output("Converting Klipper binary to custom format...")
os.system(cmd)
output_line("Done")
- config['klipper_bin_path'] = dest_bin
+ config["klipper_bin_path"] = dest_bin
###########################################################
@@ -102,14 +108,14 @@ SD_SPI_SPEED = 400000
# MCU Command Constants
RESET_CMD = "reset"
GET_CFG_CMD = "get_config"
-GET_CFG_RESPONSES = ( # Supported responses (sorted by newer revisions first).
- "config is_config=%c crc=%u is_shutdown=%c move_count=%hu", # d4aee4f
- "config is_config=%c crc=%u move_count=%hu is_shutdown=%c" # Original
+GET_CFG_RESPONSES = ( # Supported responses (sorted by newer revisions first).
+ "config is_config=%c crc=%u is_shutdown=%c move_count=%hu", # d4aee4f
+ "config is_config=%c crc=%u move_count=%hu is_shutdown=%c", # Original
)
ALLOC_OIDS_CMD = "allocate_oids count=%d"
SPI_CFG_CMDS = (
- "config_spi oid=%d pin=%s cs_active_high=%d", # 7793784
- "config_spi oid=%d pin=%s" # Original
+ "config_spi oid=%d pin=%s cs_active_high=%d", # 7793784
+ "config_spi oid=%d pin=%s", # Original
)
SPI_BUS_CMD = "spi_set_bus oid=%d spi_bus=%s mode=%d rate=%d"
SW_SPI_BUS_CMDS = (
@@ -124,31 +130,34 @@ SPI_XFER_RESPONSE = "spi_transfer_response oid=%c response=%*s"
SDIO_CFG_CMD = "config_sdio oid=%d blocksize=%u"
SDIO_BUS_CMD = "sdio_set_bus oid=%d sdio_bus=%s"
SDIO_SEND_CMD = "sdio_send_command oid=%c cmd=%c argument=%u wait=%c"
-SDIO_SEND_CMD_RESPONSE = "sdio_send_command_response oid=%c error=%c " \
- "response=%*s"
-SDIO_READ_DATA="sdio_read_data oid=%c cmd=%c argument=%u"
-SDIO_READ_DATA_RESPONSE="sdio_read_data_response oid=%c error=%c read=%u"
-SDIO_WRITE_DATA="sdio_write_data oid=%c cmd=%c argument=%u"
-SDIO_WRITE_DATA_RESPONSE="sdio_write_data_response oid=%c error=%c write=%u"
-SDIO_READ_DATA_BUFFER="sdio_read_data_buffer oid=%c offset=%u len=%c"
-SDIO_READ_DATA_BUFFER_RESPONSE="sdio_read_data_buffer_response oid=%c data=%*s"
-SDIO_WRITE_DATA_BUFFER="sdio_write_data_buffer oid=%c offset=%u data=%*s"
-SDIO_SET_SPEED="sdio_set_speed oid=%c speed=%u"
+SDIO_SEND_CMD_RESPONSE = "sdio_send_command_response oid=%c error=%c " "response=%*s"
+SDIO_READ_DATA = "sdio_read_data oid=%c cmd=%c argument=%u"
+SDIO_READ_DATA_RESPONSE = "sdio_read_data_response oid=%c error=%c read=%u"
+SDIO_WRITE_DATA = "sdio_write_data oid=%c cmd=%c argument=%u"
+SDIO_WRITE_DATA_RESPONSE = "sdio_write_data_response oid=%c error=%c write=%u"
+SDIO_READ_DATA_BUFFER = "sdio_read_data_buffer oid=%c offset=%u len=%c"
+SDIO_READ_DATA_BUFFER_RESPONSE = "sdio_read_data_buffer_response oid=%c data=%*s"
+SDIO_WRITE_DATA_BUFFER = "sdio_write_data_buffer oid=%c offset=%u data=%*s"
+SDIO_SET_SPEED = "sdio_set_speed oid=%c speed=%u"
FINALIZE_CFG_CMD = "finalize_config crc=%d"
+
class SPIFlashError(Exception):
pass
+
class MCUConfigError(SPIFlashError):
pass
+
class SPIDirect:
def __init__(self, ser):
self.oid = SPI_OID
self._spi_send_cmd = mcu.CommandWrapper(ser, SPI_SEND_CMD)
self._spi_transfer_cmd = mcu.CommandQueryWrapper(
- ser, SPI_XFER_CMD, SPI_XFER_RESPONSE, self.oid)
+ ser, SPI_XFER_CMD, SPI_XFER_RESPONSE, self.oid
+ )
def spi_send(self, data):
self._spi_send_cmd.send([self.oid, data])
@@ -156,20 +165,23 @@ class SPIDirect:
def spi_transfer(self, data):
return self._spi_transfer_cmd.send([self.oid, data])
+
class SDIODirect:
def __init__(self, ser):
self.oid = SDIO_OID
self._sdio_send_cmd = mcu.CommandQueryWrapper(
- ser, SDIO_SEND_CMD, SDIO_SEND_CMD_RESPONSE, self.oid)
+ ser, SDIO_SEND_CMD, SDIO_SEND_CMD_RESPONSE, self.oid
+ )
self._sdio_read_data = mcu.CommandQueryWrapper(
- ser, SDIO_READ_DATA, SDIO_READ_DATA_RESPONSE, self.oid)
+ ser, SDIO_READ_DATA, SDIO_READ_DATA_RESPONSE, self.oid
+ )
self._sdio_write_data = mcu.CommandQueryWrapper(
- ser, SDIO_WRITE_DATA, SDIO_WRITE_DATA_RESPONSE, self.oid)
+ ser, SDIO_WRITE_DATA, SDIO_WRITE_DATA_RESPONSE, self.oid
+ )
self._sdio_read_data_buffer = mcu.CommandQueryWrapper(
- ser, SDIO_READ_DATA_BUFFER, SDIO_READ_DATA_BUFFER_RESPONSE,
- self.oid)
- self._sdio_write_data_buffer = mcu.CommandWrapper(ser,
- SDIO_WRITE_DATA_BUFFER)
+ ser, SDIO_READ_DATA_BUFFER, SDIO_READ_DATA_BUFFER_RESPONSE, self.oid
+ )
+ self._sdio_write_data_buffer = mcu.CommandWrapper(ser, SDIO_WRITE_DATA_BUFFER)
self._sdio_set_speed = mcu.CommandWrapper(ser, SDIO_SET_SPEED)
def sdio_send_cmd(self, cmd, argument, wait):
@@ -192,14 +204,28 @@ class SDIODirect:
# FatFs Constants. Enums are implemented as lists. The item's index is its value
-DRESULT = ['RES_OK', 'RES_ERROR', 'RES_WRPRT', 'RES_NOTRDY', 'RES_PARERR']
+DRESULT = ["RES_OK", "RES_ERROR", "RES_WRPRT", "RES_NOTRDY", "RES_PARERR"]
FRESULT = [
- 'FR_OK', 'FR_DISK_ERR', 'FR_INT_ERR', 'FR_NOT_READY', 'FR_NO_FILE',
- 'FR_NO_PATH', 'FR_INVALID_NAME', 'FR_DENIED', 'FR_EXIST',
- 'FR_INVALID_OBJECT', 'FR_WRITE_PROTECTED', 'FR_INVALID_DRIVE',
- 'FR_NOT_ENABLED', 'FR_NO_FILESYSTEM', 'FR_MKFS_ABORTED', 'FR_TIMEOUT',
- 'FR_LOCKED', 'FR_NOT_ENOUGH_CORE', 'FR_TOO_MANY_OPEN_FILES',
- 'FR_INVALID_PARAMETER'
+ "FR_OK",
+ "FR_DISK_ERR",
+ "FR_INT_ERR",
+ "FR_NOT_READY",
+ "FR_NO_FILE",
+ "FR_NO_PATH",
+ "FR_INVALID_NAME",
+ "FR_DENIED",
+ "FR_EXIST",
+ "FR_INVALID_OBJECT",
+ "FR_WRITE_PROTECTED",
+ "FR_INVALID_DRIVE",
+ "FR_NOT_ENABLED",
+ "FR_NO_FILESYSTEM",
+ "FR_MKFS_ABORTED",
+ "FR_TIMEOUT",
+ "FR_LOCKED",
+ "FR_NOT_ENOUGH_CORE",
+ "FR_TOO_MANY_OPEN_FILES",
+ "FR_INVALID_PARAMETER",
]
FS_TYPES = {1: "FAT12", 2: "FAT16", 3: "FAT32", 4: "EXFAT"}
STA_NO_INIT = 1 << 0
@@ -207,6 +233,7 @@ STA_NO_DISK = 1 << 1
STA_WRITE_PROTECT = 1 << 2
SECTOR_SIZE = 512
+
# FAT16/32 File System Support
class FatFS:
def __init__(self, ser, spi=True):
@@ -220,25 +247,23 @@ class FatFS:
self._register_callbacks()
def _register_callbacks(self):
- status_cb = self.ffi_main.callback(
- "uint8_t(void)", self._fatfs_cb_status)
- init_cb = self.ffi_main.callback(
- "uint8_t(void)", self._fatfs_cb_initialize)
+ status_cb = self.ffi_main.callback("uint8_t(void)", self._fatfs_cb_status)
+ init_cb = self.ffi_main.callback("uint8_t(void)", self._fatfs_cb_initialize)
read_cb = self.ffi_main.callback(
- "uint8_t(uint8_t*, uint32_t, unsigned int)",
- self._fatfs_cb_disk_read)
+ "uint8_t(uint8_t*, uint32_t, unsigned int)", self._fatfs_cb_disk_read
+ )
write_cb = self.ffi_main.callback(
- "uint8_t(const uint8_t*, uint32_t, unsigned int)",
- self._fatfs_cb_disk_write)
+ "uint8_t(const uint8_t*, uint32_t, unsigned int)", self._fatfs_cb_disk_write
+ )
ioctl_cb = self.ffi_main.callback(
- "uint8_t(uint8_t, void*)", self._fatfs_cb_disk_ioctl)
- ftime_cb = self.ffi_main.callback(
- "uint32_t(void)", self._fatfs_cb_get_fattime)
+ "uint8_t(uint8_t, void*)", self._fatfs_cb_disk_ioctl
+ )
+ ftime_cb = self.ffi_main.callback("uint32_t(void)", self._fatfs_cb_get_fattime)
# Keep a reference to the callbacks so they don't get gc'ed
- self.ffi_callbacks = [status_cb, init_cb, read_cb, write_cb,
- ioctl_cb, ftime_cb]
+ self.ffi_callbacks = [status_cb, init_cb, read_cb, write_cb, ioctl_cb, ftime_cb]
self.ffi_lib.fatfs_set_callbacks(
- status_cb, init_cb, read_cb, write_cb, ioctl_cb, ftime_cb)
+ status_cb, init_cb, read_cb, write_cb, ioctl_cb, ftime_cb
+ )
def clear_callbacks(self):
self.ffi_lib.fatfs_clear_callbacks()
@@ -306,18 +331,27 @@ class FatFS:
# this module to take any actions for incoming IOCTL
# commands.
ioctl_cmds = [
- 'CTRL_SYNC', 'GET_SECTOR_COUNT', 'GET_SECTOR_SIZE',
- 'GET_BLOCK_SIZE', 'CTRL_TRIM']
- logging.debug("flash_sdcard: Received IOCTL command %s"
- % (ioctl_cmds[cmd]))
+ "CTRL_SYNC",
+ "GET_SECTOR_COUNT",
+ "GET_SECTOR_SIZE",
+ "GET_BLOCK_SIZE",
+ "CTRL_TRIM",
+ ]
+ logging.debug("flash_sdcard: Received IOCTL command %s" % (ioctl_cmds[cmd]))
return 0
def _fatfs_cb_get_fattime(self):
tobj = time.localtime()
year = tobj[0] - 1980
sec = min(tobj[5], 59) // 2
- return (year << 25) | (tobj[1] << 21) | (tobj[2] << 16) \
- | (tobj[3] << 11) | (tobj[4] << 5) | sec
+ return (
+ (year << 25)
+ | (tobj[1] << 21)
+ | (tobj[2] << 16)
+ | (tobj[3] << 11)
+ | (tobj[4] << 5)
+ | sec
+ )
def mount(self, print_func=logging.info):
ret = self.ffi_lib.fatfs_mount()
@@ -327,8 +361,9 @@ class FatFS:
for key, val in sorted(dinfo.items(), key=lambda x: x[0]):
print_func("%s: %s" % (key, val))
else:
- raise OSError("flash_sdcard: failed to mount SD Card, returned %s"
- % (FRESULT[ret]))
+ raise OSError(
+ "flash_sdcard: failed to mount SD Card, returned %s" % (FRESULT[ret])
+ )
def unmount(self):
self.ffi_lib.fatfs_unmount()
@@ -344,9 +379,10 @@ class FatFS:
# Can be path to directory or file
ret = self.ffi_lib.fatfs_remove(sd_path.encode())
if ret != 0:
- raise OSError("flash_sdcard: Error deleting item at path '%s',"
- " result: %s"
- % (sd_path, FRESULT[ret]))
+ raise OSError(
+ "flash_sdcard: Error deleting item at path '%s',"
+ " result: %s" % (sd_path, FRESULT[ret])
+ )
def get_file_info(self, sd_file_path):
finfo = self.ffi_main.new("struct ff_file_info *")
@@ -355,16 +391,25 @@ class FatFS:
raise OSError(
"flash_sdcard: Failed to retreive file info for path '%s',"
" result: %s"
- % (sd_file_path, FRESULT[ret],))
+ % (
+ sd_file_path,
+ FRESULT[ret],
+ )
+ )
return self._parse_ff_info(finfo)
def list_sd_directory(self, sd_dir_path):
flist = self.ffi_main.new("struct ff_file_info[128]")
ret = self.ffi_lib.fatfs_list_dir(flist, 128, sd_dir_path.encode())
if ret != 0:
- raise OSError("flash_sdcard: Failed to retreive file list at path"
- " '%s', result: %s"
- % (sd_dir_path, FRESULT[ret],))
+ raise OSError(
+ "flash_sdcard: Failed to retreive file list at path"
+ " '%s', result: %s"
+ % (
+ sd_dir_path,
+ FRESULT[ret],
+ )
+ )
convlist = []
for f in flist:
if f.size == 0 and f.modified_date == 0 and f.modified_time == 0:
@@ -377,37 +422,49 @@ class FatFS:
fdate = finfo.modified_date
ftime = finfo.modified_time
dstr = "%d-%d-%d %d:%d:%d" % (
- (fdate >> 5) & 0xF, fdate & 0x1F, ((fdate >> 9) & 0x7F) + 1980,
- (ftime >> 11) & 0x1F, (ftime >> 5) & 0x3F, ftime & 0x1F)
+ (fdate >> 5) & 0xF,
+ fdate & 0x1F,
+ ((fdate >> 9) & 0x7F) + 1980,
+ (ftime >> 11) & 0x1F,
+ (ftime >> 5) & 0x3F,
+ ftime & 0x1F,
+ )
return {
- 'name': self.ffi_main.string(finfo.name, 256),
- 'size': finfo.size,
- 'modified': dstr,
- 'is_dir': bool(finfo.attrs & 0x10),
- 'is_read_only': bool(finfo.attrs & 0x01),
- 'is_hidden': bool(finfo.attrs & 0x02),
- 'is_system': bool(finfo.attrs & 0x04)
+ "name": self.ffi_main.string(finfo.name, 256),
+ "size": finfo.size,
+ "modified": dstr,
+ "is_dir": bool(finfo.attrs & 0x10),
+ "is_read_only": bool(finfo.attrs & 0x01),
+ "is_hidden": bool(finfo.attrs & 0x02),
+ "is_system": bool(finfo.attrs & 0x04),
}
def get_disk_info(self):
disk_info = self.ffi_main.new("struct ff_disk_info *")
ret = self.ffi_lib.fatfs_get_disk_info(disk_info)
if ret != 0:
- logging.info("flash_sdcard: Failed to retreive disk info: %s"
- % (FRESULT[ret],))
+ logging.info(
+ "flash_sdcard: Failed to retreive disk info: %s" % (FRESULT[ret],)
+ )
return {}
return {
- 'volume_label': self.ffi_main.string(disk_info.label, 12),
- 'volume_serial': disk_info.serial_number,
- 'fs_type': FS_TYPES.get(disk_info.fs_type, "UNKNOWN")
+ "volume_label": self.ffi_main.string(disk_info.label, 12),
+ "volume_serial": disk_info.serial_number,
+ "fs_type": FS_TYPES.get(disk_info.fs_type, "UNKNOWN"),
}
SD_FILE_MODES = {
- 'w+x': 0x01 | 0x02 | 0x04, 'wx': 0x02 | 0x04,
- 'r+': 0x01 | 0x02, 'w+': 0x01 | 0x02 | 0x08,
- 'a+': 0x01 | 0x02 | 0x30, 'r': 0x01,
- 'w': 0x02 | 0x08, 'a': 0x02 | 0x30}
+ "w+x": 0x01 | 0x02 | 0x04,
+ "wx": 0x02 | 0x04,
+ "r+": 0x01 | 0x02,
+ "w+": 0x01 | 0x02 | 0x08,
+ "a+": 0x01 | 0x02 | 0x30,
+ "r": 0x01,
+ "w": 0x02 | 0x08,
+ "a": 0x02 | 0x30,
+}
+
class SDCardFile:
def __init__(self, ffi_main, ffi_lib, sd_path, mode="r"):
@@ -415,7 +472,7 @@ class SDCardFile:
self.ffi_lib = ffi_lib
self.path = sd_path
mode = mode.lower()
- if mode[-1] == 'b':
+ if mode[-1] == "b":
mode = mode[:-1]
self.mode = SD_FILE_MODES.get(mode, 0)
self.fhdl = None
@@ -429,8 +486,7 @@ class SDCardFile:
self.eof = False
if self.fhdl == self.ffi_main.NULL:
self.fhdl = None
- raise OSError("flash_sdcard: Could not open file '%s':"
- % (self.path))
+ raise OSError("flash_sdcard: Could not open file '%s':" % (self.path))
def read(self, length=None):
if self.fhdl is None:
@@ -448,9 +504,8 @@ class SDCardFile:
while True:
bytes_read = self.ffi_lib.fatfs_read(self.fhdl, cdata_buf, length)
if bytes_read < 0:
- raise OSError("flash_sdcard: Error Reading file '%s'"
- % (self.path))
- self.eof = (bytes_read < length)
+ raise OSError("flash_sdcard: Error Reading file '%s'" % (self.path))
+ self.eof = bytes_read < length
ret_buf += byte_buf[0:bytes_read]
if self.eof or not full_read:
break
@@ -473,9 +528,10 @@ class SDCardFile:
ret = self.ffi_lib.fatfs_close(self.fhdl)
self.fhdl = None
if ret != 0:
- logging.info("flash_sdcard: Error closing sd file '%s', "
- "returned %d"
- % (self.path, FRESULT[ret]))
+ logging.info(
+ "flash_sdcard: Error closing sd file '%s', "
+ "returned %d" % (self.path, FRESULT[ret])
+ )
def __enter__(self):
self.open()
@@ -486,24 +542,25 @@ class SDCardFile:
SD_COMMANDS = {
- 'GO_IDLE_STATE': 0,
- 'ALL_SEND_CID': 2,
- 'SET_REL_ADDR': 3,
- 'SET_BUS_WIDTH': 6,
- 'SEL_DESEL_CARD': 7,
- 'SEND_IF_COND': 8,
- 'SEND_CSD': 9,
- 'SEND_CID': 10,
- 'SD_SEND_OP_COND': 41,
- 'SEND_STATUS': 13,
- 'SET_BLOCKLEN': 16,
- 'READ_SINGLE_BLOCK': 17,
- 'WRITE_BLOCK': 24,
- 'APP_CMD': 55,
- 'READ_OCR': 58,
- 'CRC_ON_OFF': 59,
+ "GO_IDLE_STATE": 0,
+ "ALL_SEND_CID": 2,
+ "SET_REL_ADDR": 3,
+ "SET_BUS_WIDTH": 6,
+ "SEL_DESEL_CARD": 7,
+ "SEND_IF_COND": 8,
+ "SEND_CSD": 9,
+ "SEND_CID": 10,
+ "SD_SEND_OP_COND": 41,
+ "SEND_STATUS": 13,
+ "SET_BLOCKLEN": 16,
+ "READ_SINGLE_BLOCK": 17,
+ "WRITE_BLOCK": 24,
+ "APP_CMD": 55,
+ "READ_OCR": 58,
+ "CRC_ON_OFF": 59,
}
+
class SDCardSPI:
def __init__(self, ser):
self.spi = SPIDirect(ser)
@@ -522,18 +579,20 @@ class SDCardSPI:
if self.initialized:
return
# Send reset command (CMD0)
- if not self._check_command(1, 'GO_IDLE_STATE', 0):
+ if not self._check_command(1, "GO_IDLE_STATE", 0):
raise OSError(
"flash_sdcard: failed to reset SD Card\n"
"Note that older (Version 1.0) SD cards can not be\n"
"hot swapped. Execute FIRMWARE_RESTART with the card\n"
- "inserted for successful initialization.")
+ "inserted for successful initialization."
+ )
# Check Voltage Range (CMD8). Only Cards meeting the v2.0 spec
# support this. V1.0 cards (and MMC) will return illegal command.
check_pattern = 0b1010
resp = self._send_command_with_response(
- 'SEND_IF_COND', (1 << 8) | check_pattern)
- resp = resp.strip(b'\xFF')
+ "SEND_IF_COND", (1 << 8) | check_pattern
+ )
+ resp = resp.strip(b"\xff")
if resp and resp[0] & (1 << 2):
# CMD8 is illegal, this is a version 1.0 card
self.sd_version = 1
@@ -541,54 +600,57 @@ class SDCardSPI:
if resp[0] == 1:
self.sd_version = 2
if not (resp[-2] == 1 and resp[-1] == check_pattern):
- raise OSError("flash_sdcard: SD Card not running in a "
- "compatible voltage range")
+ raise OSError(
+ "flash_sdcard: SD Card not running in a "
+ "compatible voltage range"
+ )
else:
raise OSError("flash_sdcard: CMD8 Error 0x%X" % (resp[0],))
else:
- raise OSError("flash_sdcard: Invalid CMD8 response: %s"
- % (repr(resp)))
+ raise OSError("flash_sdcard: Invalid CMD8 response: %s" % (repr(resp)))
if self.enable_crc:
# Enable SD crc checks (CMD59)
- if not self._check_command(1, 'CRC_ON_OFF', 1):
+ if not self._check_command(1, "CRC_ON_OFF", 1):
logging.info("flash_sdcard: failed to enable CRC checks")
if self.sd_version == 2:
# Init card and come out of idle (ACMD41)
# Version 2 Cards may init before checking the OCR
- if not self._check_command(0, 'SD_SEND_OP_COND', 1 << 30,
- is_app_cmd=True):
- raise OSError("flash_sdcard: SD Card did not come"
- " out of IDLE after reset")
+ if not self._check_command(
+ 0, "SD_SEND_OP_COND", 1 << 30, is_app_cmd=True
+ ):
+ raise OSError(
+ "flash_sdcard: SD Card did not come" " out of IDLE after reset"
+ )
# Read OCR Register (CMD58)
- resp = self._send_command_with_response('READ_OCR', 0)
- resp = resp.strip(b'\xFF')
+ resp = self._send_command_with_response("READ_OCR", 0)
+ resp = resp.strip(b"\xff")
# If 'READ_OCR' is illegal then this is likely MMC.
# At this time MMC is not supported
if len(resp) == 5:
if self.sd_version == 1 and resp[0] == 1:
# Check acceptable voltage range for V1 cards
if resp[2] != 0xFF:
- raise OSError("flash_sdcard: card does not support"
- " 3.3v range")
+ raise OSError(
+ "flash_sdcard: card does not support" " 3.3v range"
+ )
elif self.sd_version == 2 and resp[0] == 0:
# Determine if this is a high capacity sdcard
if resp[1] & 0x40:
self.high_capacity = True
else:
- raise OSError("flash_sdcard: READ_OCR Error 0x%X"
- % (resp[0],))
+ raise OSError("flash_sdcard: READ_OCR Error 0x%X" % (resp[0],))
else:
raise OSError("flash_sdcard: Invalid OCR Response")
if self.sd_version == 1:
# Init card and come out of idle (ACMD41)
# Version 1 Cards do this after checking the OCR
- if not self._check_command(0, 'SD_SEND_OP_COND', 0,
- is_app_cmd=True):
- raise OSError("flash_sdcard: SD Card did not come"
- " out of IDLE after reset")
+ if not self._check_command(0, "SD_SEND_OP_COND", 0, is_app_cmd=True):
+ raise OSError(
+ "flash_sdcard: SD Card did not come" " out of IDLE after reset"
+ )
# Set block size to 512 (CMD16)
- if self._check_command(0, 'SET_BLOCKLEN', SECTOR_SIZE, tries=5):
+ if self._check_command(0, "SET_BLOCKLEN", SECTOR_SIZE, tries=5):
self.initialized = True
else:
raise OSError("flash_sdcard: failed to set block size")
@@ -601,10 +663,10 @@ class SDCardSPI:
if self.initialized:
# Reset the SD Card
try:
- if not self._check_command(1, 'GO_IDLE_STATE', 0):
+ if not self._check_command(1, "GO_IDLE_STATE", 0):
logging.info("flash_sdcard: failed to reset SD Card")
# Disable CRC Checks
- if not self._check_command(1, 'CRC_ON_OFF', 0):
+ if not self._check_command(1, "CRC_ON_OFF", 0):
logging.info("flash_sdcard: failed to disable CRC")
except Exception:
logging.exception("Error resetting SD Card")
@@ -615,26 +677,29 @@ class SDCardSPI:
self.card_info.clear()
def _check_command(self, expected, cmd, args, is_app_cmd=False, tries=15):
- func = self._send_app_cmd_with_response if is_app_cmd else \
- self._send_command_with_response
+ func = (
+ self._send_app_cmd_with_response
+ if is_app_cmd
+ else self._send_command_with_response
+ )
while True:
resp, rt = func(cmd, args, get_rt=True)
# logging.info("flash_sdcard: Check cmd %s, response: %s"
# % (cmd, repr(resp)))
- resp = resp.strip(b'\xFF')
+ resp = resp.strip(b"\xff")
if resp and expected == resp[0]:
return True
tries -= 1
if tries < 1:
return False
- self.reactor.pause(rt + .1)
+ self.reactor.pause(rt + 0.1)
def _send_command(self, cmd, args):
command = SD_COMMANDS[cmd] | 0x40
request = [command]
if isinstance(args, int):
for i in range(3, -1, -1):
- request.append((args >> (8*i)) & 0xFF)
+ request.append((args >> (8 * i)) & 0xFF)
elif isinstance(args, list) and len(args) == 4:
request += args
else:
@@ -645,22 +710,22 @@ class SDCardSPI:
def _send_command_with_response(self, cmd, args, get_rt=False):
self._send_command(cmd, args)
- params = self.spi.spi_transfer([0xFF]*8)
+ params = self.spi.spi_transfer([0xFF] * 8)
if get_rt:
- return bytearray(params['response']), params['#receive_time']
+ return bytearray(params["response"]), params["#receive_time"]
else:
- return bytearray(params['response'])
+ return bytearray(params["response"])
def _send_app_cmd_with_response(self, cmd, args, get_rt=False):
# CMD55 tells the SD Card that the next command is an
# Application Specific Command.
- self._send_command_with_response('APP_CMD', 0)
+ self._send_command_with_response("APP_CMD", 0)
return self._send_command_with_response(cmd, args, get_rt)
def _find_sd_token(self, token, tries=10):
while tries:
params = self.spi.spi_transfer([0xFF])
- resp = bytearray(params['response'])
+ resp = bytearray(params["response"])
if resp[0] == token:
return True
tries -= 1
@@ -669,36 +734,35 @@ class SDCardSPI:
def _find_sd_response(self, tries=10):
while tries:
params = self.spi.spi_transfer([0xFF])
- resp = bytearray(params['response'])
+ resp = bytearray(params["response"])
if resp[0] != 0xFF:
return resp[0]
tries -= 1
return 0xFF
def _process_cid_reg(self):
- self._send_command('SEND_CID', 0)
+ self._send_command("SEND_CID", 0)
reg = self._do_block_read(size=16)
if reg is None:
raise OSError("flash_sdcard: Error reading CID register")
cid = collections.OrderedDict()
- cid['manufacturer_id'] = reg[0]
- cid['oem_id'] = reg[1:3].decode(encoding='ascii', errors='ignore')
- cid['product_name'] = reg[3:8].decode(
- encoding='ascii', errors='ignore')
- cid['product_revision'] = str(reg[8] >> 4 & 0xFF) + "." \
- + str(reg[8] & 0xFF)
- cid['serial_number'] = "".join(["%02X" % (c,) for c in reg[9:13]])
+ cid["manufacturer_id"] = reg[0]
+ cid["oem_id"] = reg[1:3].decode(encoding="ascii", errors="ignore")
+ cid["product_name"] = reg[3:8].decode(encoding="ascii", errors="ignore")
+ cid["product_revision"] = str(reg[8] >> 4 & 0xFF) + "." + str(reg[8] & 0xFF)
+ cid["serial_number"] = "".join(["%02X" % (c,) for c in reg[9:13]])
mfg_year = (((reg[13] & 0xF) << 4) | ((reg[14] >> 4) & 0xF)) + 2000
mfg_month = reg[14] & 0xF
- cid['manufacturing_date'] = "%d/%d" % (mfg_month, mfg_year)
+ cid["manufacturing_date"] = "%d/%d" % (mfg_month, mfg_year)
crc = calc_crc7(reg[:15])
if crc != reg[15]:
- raise OSError("flash_sdcard: CID crc mismatch: 0x%02X, recd: 0x%02X"
- % (crc, reg[15]))
+ raise OSError(
+ "flash_sdcard: CID crc mismatch: 0x%02X, recd: 0x%02X" % (crc, reg[15])
+ )
self.card_info.update(cid)
def _process_csd_reg(self):
- self._send_command('SEND_CSD', 0)
+ self._send_command("SEND_CSD", 0)
reg = self._do_block_read(size=16)
if reg is None:
raise OSError("flash_sdcard: Error reading CSD register")
@@ -707,10 +771,9 @@ class SDCardSPI:
csd_type = (reg[0] >> 6) & 0x3
if csd_type == 0:
# Standard Capacity (CSD Version 1.0)
- max_block_len = 2**(reg[5] & 0xF)
- c_size = ((reg[6] & 0x3) << 10) | (reg[7] << 2) | \
- ((reg[8] >> 6) & 0x3)
- c_mult = 2**((((reg[9] & 0x3) << 1) | (reg[10] >> 7)) + 2)
+ max_block_len = 2 ** (reg[5] & 0xF)
+ c_size = ((reg[6] & 0x3) << 10) | (reg[7] << 2) | ((reg[8] >> 6) & 0x3)
+ c_mult = 2 ** ((((reg[9] & 0x3) << 1) | (reg[10] >> 7)) + 2)
max_capacity = (c_size + 1) * c_mult * max_block_len
str_capacity = "%.1f MiB" % (max_capacity / (1024.0**2))
elif csd_type == 1:
@@ -723,9 +786,10 @@ class SDCardSPI:
self.write_protected = (reg[14] & 0x30) != 0
crc = calc_crc7(reg[:15])
if crc != reg[15]:
- raise OSError("flash_sdcard: CSD crc mismatch: 0x%02X, recd: 0x%02X"
- % (crc, reg[15]))
- self.card_info['capacity'] = str_capacity
+ raise OSError(
+ "flash_sdcard: CSD crc mismatch: 0x%02X, recd: 0x%02X" % (crc, reg[15])
+ )
+ self.card_info["capacity"] = str_capacity
self.total_sectors = max_capacity // SECTOR_SIZE
def print_card_info(self, print_func=logging.info):
@@ -749,7 +813,7 @@ class SDCardSPI:
offset = sector
if not self.high_capacity:
offset = sector * SECTOR_SIZE
- self._send_command('READ_SINGLE_BLOCK', offset)
+ self._send_command("READ_SINGLE_BLOCK", offset)
buf = self._do_block_read()
if buf is None:
raise OSError(err_msg)
@@ -759,12 +823,12 @@ class SDCardSPI:
valid_response = True
sd_resp = self._find_sd_response()
if sd_resp != 0:
- logging.info("flash_sdcard: invalid read block response: 0x%02X"
- % (sd_resp))
+ logging.info(
+ "flash_sdcard: invalid read block response: 0x%02X" % (sd_resp)
+ )
valid_response = False
if not self._find_sd_token(0xFE):
- logging.info("flash_sdcard: read error, unable to find "
- "start token")
+ logging.info("flash_sdcard: read error, unable to find " "start token")
valid_response = False
if not valid_response:
# In the event of an invalid response we will still
@@ -773,26 +837,27 @@ class SDCardSPI:
bcount = size + 2
while bcount:
sent = min(32, bcount)
- self.spi.spi_send([0xFF]*sent)
+ self.spi.spi_send([0xFF] * sent)
bcount -= sent
self._find_sd_token(0xFF)
return None
buf = bytearray()
while len(buf) < size:
count = min(32, size - len(buf))
- params = self.spi.spi_transfer([0xFF]*count)
- buf += bytearray(params['response'])
+ params = self.spi.spi_transfer([0xFF] * count)
+ buf += bytearray(params["response"])
# Get the CRC
params = self.spi.spi_transfer([0xFF, 0xFF])
# Make sure we leave the busy state
self._find_sd_token(0xFF)
- crc = bytearray(params['response'])
+ crc = bytearray(params["response"])
crc_int = (crc[0] << 8) | crc[1]
calculated_crc = calc_crc16(buf)
if calculated_crc != crc_int:
logging.info(
"flash_sdcard: CRC Mismatch, Received: %04X, Calculated: %04X"
- % (crc_int, calculated_crc))
+ % (crc_int, calculated_crc)
+ )
return None
return buf
@@ -800,24 +865,23 @@ class SDCardSPI:
with self.mutex:
if not 0 <= sector < self.total_sectors:
raise OSError(
- "flash_sdcard: write error, sector number %d invalid"
- % (sector))
+ "flash_sdcard: write error, sector number %d invalid" % (sector)
+ )
if not self.initialized:
- raise OSError("flash_sdcard: write error, SD Card not"
- " initialized")
+ raise OSError("flash_sdcard: write error, SD Card not" " initialized")
outbuf = bytearray(data)
if len(outbuf) > SECTOR_SIZE:
- raise OSError("sd_card: Cannot write sector larger"
- " than %d bytes"
- % (SECTOR_SIZE))
+ raise OSError(
+ "sd_card: Cannot write sector larger"
+ " than %d bytes" % (SECTOR_SIZE)
+ )
elif len(outbuf) < SECTOR_SIZE:
outbuf += bytearray([0] * (SECTOR_SIZE - len(outbuf)))
offset = sector
if not self.high_capacity:
offset = sector * SECTOR_SIZE
- if not self._check_command(0, 'WRITE_BLOCK', offset, tries=2):
- raise OSError("flash_sdcard: Error writing to sector %d"
- % (sector,))
+ if not self._check_command(0, "WRITE_BLOCK", offset, tries=2):
+ raise OSError("flash_sdcard: Error writing to sector %d" % (sector,))
crc = calc_crc16(outbuf)
outbuf.insert(0, 0xFE)
outbuf.append((crc >> 8) & 0xFF)
@@ -827,26 +891,30 @@ class SDCardSPI:
outbuf = outbuf[32:]
resp = self._find_sd_response()
err_msgs = []
- if (resp & 0x1f) != 5:
+ if (resp & 0x1F) != 5:
err_msgs.append("flash_sdcard: write error 0x%02X" % (resp,))
# wait until the card leaves the busy state
if not self._find_sd_token(0xFF, tries=128):
- err_msgs.append("flash_sdcard: could not leave busy"
- " state after write")
+ err_msgs.append(
+ "flash_sdcard: could not leave busy" " state after write"
+ )
else:
- status = self._send_command_with_response('SEND_STATUS', 0)
- status = status.strip(b'\xFF')
+ status = self._send_command_with_response("SEND_STATUS", 0)
+ status = status.strip(b"\xff")
if len(status) == 2:
if status[1] != 0:
err_msgs.append(
- "flash_sdcard: write error 0x%02X"
- % (status[1],))
+ "flash_sdcard: write error 0x%02X" % (status[1],)
+ )
else:
- err_msgs.append("flash_sdcard: Invalid status response"
- " after write: %s" % (repr(status),))
+ err_msgs.append(
+ "flash_sdcard: Invalid status response"
+ " after write: %s" % (repr(status),)
+ )
if err_msgs:
raise OSError("\n".join(err_msgs))
+
class SDCardSDIO:
def __init__(self, ser):
self.sdio = SDIODirect(ser)
@@ -864,49 +932,61 @@ class SDCardSDIO:
def init_sd(self):
def check_for_ocr_errors(reg):
# returns False if an error flag is set
- return ((reg[0]&0xFD) | (reg[1]&0xFF) |
- (reg[2]&0xE0) | (reg[3]&0x08)) == 0
+ return (
+ (reg[0] & 0xFD) | (reg[1] & 0xFF) | (reg[2] & 0xE0) | (reg[3] & 0x08)
+ ) == 0
+
with self.mutex:
if self.initialized:
return
# Send reset command (CMD0)
- if not self._send_command('GO_IDLE_STATE', 0):
+ if not self._send_command("GO_IDLE_STATE", 0):
raise OSError(
"flash_sdcard: failed to reset SD Card\n"
"Note that older (Version 1.0) SD cards can not be\n"
"hot swapped. Execute FIRMWARE_RESTART with the card\n"
- "inserted for successful initialization.")
+ "inserted for successful initialization."
+ )
# Check Voltage Range (CMD8). Only Cards meeting the v2.0 spec
# support this. V1.0 cards (and MMC) will return illegal command.
check_pattern = 0b1010
resp = self._send_command_with_response(
- 'SEND_IF_COND', (1 << 8) | check_pattern)
- resp = resp.strip(b'\xFF')
+ "SEND_IF_COND", (1 << 8) | check_pattern
+ )
+ resp = resp.strip(b"\xff")
if len(resp) != 4:
# CMD8 is illegal, this is a version 1.0 card
self.sd_version = 1
else:
self.sd_version = 2
if not (resp[-2] == 1 and resp[-1] == check_pattern):
- raise OSError("flash_sdcard: SD Card not running in a "
- "compatible voltage range")
+ raise OSError(
+ "flash_sdcard: SD Card not running in a "
+ "compatible voltage range"
+ )
if self.sd_version == 2:
# Init card and come out of idle (ACMD41)
# Version 2 Cards may init before checking the OCR
# Allow vor LVDS card with 1.8v, too.
- resp = self._check_command(lambda x: x[0]>>7 == 1,
- 'SD_SEND_OP_COND', 0xC1100000, is_app_cmd=True,
- ignoreCRC=True)
+ resp = self._check_command(
+ lambda x: x[0] >> 7 == 1,
+ "SD_SEND_OP_COND",
+ 0xC1100000,
+ is_app_cmd=True,
+ ignoreCRC=True,
+ )
if resp is None:
- raise OSError("flash_sdcard: SD Card did not come"
- " out of IDLE after reset")
+ raise OSError(
+ "flash_sdcard: SD Card did not come" " out of IDLE after reset"
+ )
if len(resp) == 4:
if self.sd_version == 1:
# Check acceptable voltage range for V1 cards
if resp[1] != 0xFF:
- raise OSError("flash_sdcard: card does not support"
- " 3.3v range")
+ raise OSError(
+ "flash_sdcard: card does not support" " 3.3v range"
+ )
elif self.sd_version == 2:
# Determine if this is a high capacity sdcard
if resp[0] & 0x40:
@@ -916,46 +996,46 @@ class SDCardSDIO:
if self.sd_version == 1:
# Init card and come out of idle (ACMD41)
# Version 1 Cards do this after checking the OCR
- if not self._check_command(0, 'SD_SEND_OP_COND', 0,
- is_app_cmd=True):
- raise OSError("flash_sdcard: SD Card did not come"
- " out of IDLE after reset")
+ if not self._check_command(0, "SD_SEND_OP_COND", 0, is_app_cmd=True):
+ raise OSError(
+ "flash_sdcard: SD Card did not come" " out of IDLE after reset"
+ )
# Read out CID information register
self._process_cid_reg()
# Get card's relative address (RCA)
- resp = self._send_command_with_response('SET_REL_ADDR', 0)
+ resp = self._send_command_with_response("SET_REL_ADDR", 0)
# Check if bits 15:13 have some error set
- if (resp[-2] & 0xe0) != 0:
- raise OSError("flash_sdcard: set card's "
- "relative address failed")
- self.rca = resp[0]<<8 | resp[1]
+ if (resp[-2] & 0xE0) != 0:
+ raise OSError("flash_sdcard: set card's " "relative address failed")
+ self.rca = resp[0] << 8 | resp[1]
# Read out CSD information register
self._process_csd_reg()
# Select the current card
- if not self._check_command(check_for_ocr_errors, 'SEL_DESEL_CARD',
- self.rca << 16, tries=1):
+ if not self._check_command(
+ check_for_ocr_errors, "SEL_DESEL_CARD", self.rca << 16, tries=1
+ ):
raise OSError("flash_sdcard: failed to select the card")
# Set SDIO clk speed to approx. 1 MHz
self.sdio.sdio_set_speed(1000000)
- if self._check_command(check_for_ocr_errors, 'SET_BLOCKLEN',
- SECTOR_SIZE, tries=5):
+ if self._check_command(
+ check_for_ocr_errors, "SET_BLOCKLEN", SECTOR_SIZE, tries=5
+ ):
self.initialized = True
else:
raise OSError("flash_sdcard: failed to set block size")
-
def deinit(self):
with self.mutex:
if self.initialized:
# Reset the SD Card
try:
- if not self._send_command('GO_IDLE_STATE', 0):
+ if not self._send_command("GO_IDLE_STATE", 0):
logging.info("flash_sdcard: failed to reset SD Card")
except Exception:
logging.exception("Error resetting SD Card")
@@ -965,20 +1045,24 @@ class SDCardSDIO:
self.total_sectors = 0
self.card_info.clear()
- def _check_command(self, check_func, cmd, args, is_app_cmd=False, tries=15,
- ignoreCRC=False):
- func = self._send_app_cmd_with_response if is_app_cmd else \
- self._send_command_with_response
+ def _check_command(
+ self, check_func, cmd, args, is_app_cmd=False, tries=15, ignoreCRC=False
+ ):
+ func = (
+ self._send_app_cmd_with_response
+ if is_app_cmd
+ else self._send_command_with_response
+ )
while True:
resp, rt = func(cmd, args, get_rt=True, ignoreCRC=ignoreCRC)
- #logging.info("flash_sdcard: Check cmd %s, response: %s"
+ # logging.info("flash_sdcard: Check cmd %s, response: %s"
# % (cmd, repr(resp)))
if resp and check_func(resp):
return resp
tries -= 1
if tries < 1:
return None
- self.reactor.pause(rt + .1)
+ self.reactor.pause(rt + 0.1)
def _send_command(self, cmd, args, wait=0):
cmd_code = SD_COMMANDS[cmd]
@@ -986,68 +1070,70 @@ class SDCardSDIO:
if isinstance(args, int) or isinstance(args, long):
argument = args & 0xFFFFFFFF
elif isinstance(args, list) and len(args) == 4:
- argument = ((args[0] << 24) & 0xFF000000) | \
- ((args[1] << 16) & 0x00FF0000) | \
- ((args[2] << 8) & 0x0000FF00) | \
- ((args[3] << 0) & 0x000000FF)
+ argument = (
+ ((args[0] << 24) & 0xFF000000)
+ | ((args[1] << 16) & 0x00FF0000)
+ | ((args[2] << 8) & 0x0000FF00)
+ | ((args[3] << 0) & 0x000000FF)
+ )
else:
raise OSError("flash_sdcard: Invalid SD Card Command argument")
params = self.sdio.sdio_send_cmd(cmd_code, argument, wait)
- #logging.debug(f'_send_command({cmd=}, {args=}, {wait=}) -> '
+ # logging.debug(f'_send_command({cmd=}, {args=}, {wait=}) -> '
# 'CMD: {cmd_code} ARG: {argument} -> {params=}')
- if (wait == 0):
+ if wait == 0:
# Just return the error code if no response was requested
- return params['error'] == 0
+ return params["error"] == 0
return params
- def _send_command_with_response(self, cmd, args, check_error=True,
- ignoreCRC=False, get_rt=False):
+ def _send_command_with_response(
+ self, cmd, args, check_error=True, ignoreCRC=False, get_rt=False
+ ):
# Wait for a short response
params = self._send_command(cmd, args, wait=1)
- response = params['response']
+ response = params["response"]
if check_error:
- if params['error'] != 0:
- if ignoreCRC and params['error'] != 4:
+ if params["error"] != 0:
+ if ignoreCRC and params["error"] != 4:
response = []
if get_rt:
- return bytearray(response), params['#receive_time']
+ return bytearray(response), params["#receive_time"]
else:
return bytearray(response)
- def _send_app_cmd_with_response(self, cmd, args,
- ignoreCRC=False, get_rt=False):
+ def _send_app_cmd_with_response(self, cmd, args, ignoreCRC=False, get_rt=False):
# CMD55 tells the SD Card that the next command is an
# Application Specific Command.
- self._send_command_with_response('APP_CMD', self.rca << 16)
+ self._send_command_with_response("APP_CMD", self.rca << 16)
return self._send_command_with_response(
- cmd, args, ignoreCRC=ignoreCRC, get_rt=get_rt)
+ cmd, args, ignoreCRC=ignoreCRC, get_rt=get_rt
+ )
def _process_cid_reg(self):
- params = self._send_command('ALL_SEND_CID', 0, wait=2)
- reg = bytearray(params['response'])
+ params = self._send_command("ALL_SEND_CID", 0, wait=2)
+ reg = bytearray(params["response"])
if reg is None:
raise OSError("flash_sdcard: Error reading CID register")
cid = collections.OrderedDict()
- cid['manufacturer_id'] = reg[0]
- cid['oem_id'] = reg[1:3].decode(encoding='ascii', errors='ignore')
- cid['product_name'] = reg[3:8].decode(
- encoding='ascii', errors='ignore')
- cid['product_revision'] = str(reg[8] >> 4 & 0xFF) + "." \
- + str(reg[8] & 0xFF)
- cid['serial_number'] = "".join(["%02X" % (c,) for c in reg[9:13]])
+ cid["manufacturer_id"] = reg[0]
+ cid["oem_id"] = reg[1:3].decode(encoding="ascii", errors="ignore")
+ cid["product_name"] = reg[3:8].decode(encoding="ascii", errors="ignore")
+ cid["product_revision"] = str(reg[8] >> 4 & 0xFF) + "." + str(reg[8] & 0xFF)
+ cid["serial_number"] = "".join(["%02X" % (c,) for c in reg[9:13]])
mfg_year = (((reg[13] & 0xF) << 4) | ((reg[14] >> 4) & 0xF)) + 2000
mfg_month = reg[14] & 0xF
- cid['manufacturing_date'] = "%d/%d" % (mfg_month, mfg_year)
+ cid["manufacturing_date"] = "%d/%d" % (mfg_month, mfg_year)
crc = calc_crc7(reg[:15], with_padding=False)
if crc != reg[15]:
- raise OSError("flash_sdcard: CID crc mismatch: 0x%02X, recd: 0x%02X"
- % (crc, reg[15]))
+ raise OSError(
+ "flash_sdcard: CID crc mismatch: 0x%02X, recd: 0x%02X" % (crc, reg[15])
+ )
self.card_info.update(cid)
def _process_csd_reg(self):
- params = self._send_command('SEND_CSD', self.rca << 16, wait=2)
- reg = bytearray(params['response'])
+ params = self._send_command("SEND_CSD", self.rca << 16, wait=2)
+ reg = bytearray(params["response"])
if reg is None:
raise OSError("flash_sdcard: Error reading CSD register")
str_capacity = "Invalid"
@@ -1055,10 +1141,9 @@ class SDCardSDIO:
csd_type = (reg[0] >> 6) & 0x3
if csd_type == 0:
# Standard Capacity (CSD Version 1.0)
- max_block_len = 2**(reg[5] & 0xF)
- c_size = ((reg[6] & 0x3) << 10) | (reg[7] << 2) | \
- ((reg[8] >> 6) & 0x3)
- c_mult = 2**((((reg[9] & 0x3) << 1) | (reg[10] >> 7)) + 2)
+ max_block_len = 2 ** (reg[5] & 0xF)
+ c_size = ((reg[6] & 0x3) << 10) | (reg[7] << 2) | ((reg[8] >> 6) & 0x3)
+ c_mult = 2 ** ((((reg[9] & 0x3) << 1) | (reg[10] >> 7)) + 2)
max_capacity = (c_size + 1) * c_mult * max_block_len
str_capacity = "%.1f MiB" % (max_capacity / (1024.0**2))
elif csd_type == 1:
@@ -1071,9 +1156,10 @@ class SDCardSDIO:
self.write_protected = (reg[14] & 0x30) != 0
crc = calc_crc7(reg[:15], with_padding=False)
if crc != reg[15]:
- raise OSError("flash_sdcard: CSD crc mismatch: 0x%02X, recd: 0x%02X"
- % (crc, reg[15]))
- self.card_info['capacity'] = str_capacity
+ raise OSError(
+ "flash_sdcard: CSD crc mismatch: 0x%02X, recd: 0x%02X" % (crc, reg[15])
+ )
+ self.card_info["capacity"] = str_capacity
self.total_sectors = max_capacity // SECTOR_SIZE
def print_card_info(self, print_func=logging.info):
@@ -1099,22 +1185,24 @@ class SDCardSDIO:
offset = sector * SECTOR_SIZE
params = self.sdio.sdio_read_data(
- SD_COMMANDS['READ_SINGLE_BLOCK'], offset)
- if params['error'] != 0:
+ SD_COMMANDS["READ_SINGLE_BLOCK"], offset
+ )
+ if params["error"] != 0:
raise OSError(
- 'Read data failed. Error code=%d' %(params['error'],) )
- if params['read'] != SECTOR_SIZE:
+ "Read data failed. Error code=%d" % (params["error"],)
+ )
+ if params["read"] != SECTOR_SIZE:
raise OSError(
- 'Read data failed. Expected %d bytes but got %d.' %
- (SECTOR_SIZE, params['read']) )
+ "Read data failed. Expected %d bytes but got %d."
+ % (SECTOR_SIZE, params["read"])
+ )
buf = bytearray()
offset = 0
- while SECTOR_SIZE-len(buf)>0:
- rest = min(SECTOR_SIZE-len(buf), 32)
- params = self.sdio.sdio_read_data_buffer(
- offset, length=rest)
- temp = bytearray(params['data'])
+ while SECTOR_SIZE - len(buf) > 0:
+ rest = min(SECTOR_SIZE - len(buf), 32)
+ params = self.sdio.sdio_read_data_buffer(offset, length=rest)
+ temp = bytearray(params["data"])
if len(temp) == 0:
raise OSError("Read zero bytes from buffer")
buf += temp
@@ -1127,16 +1215,16 @@ class SDCardSDIO:
with self.mutex:
if not 0 <= sector < self.total_sectors:
raise OSError(
- "flash_sdcard: write error, sector number %d invalid"
- % (sector))
+ "flash_sdcard: write error, sector number %d invalid" % (sector)
+ )
if not self.initialized:
- raise OSError("flash_sdcard: write error, SD Card not"
- " initialized")
+ raise OSError("flash_sdcard: write error, SD Card not" " initialized")
outbuf = bytearray(data)
if len(outbuf) > SECTOR_SIZE:
- raise OSError("sd_card: Cannot write sector larger"
- " than %d bytes"
- % (SECTOR_SIZE))
+ raise OSError(
+ "sd_card: Cannot write sector larger"
+ " than %d bytes" % (SECTOR_SIZE)
+ )
elif len(outbuf) < SECTOR_SIZE:
outbuf += bytearray([0] * (SECTOR_SIZE - len(outbuf)))
offset = sector
@@ -1145,23 +1233,25 @@ class SDCardSDIO:
CHUNKSIZE = 32
for i in range(0, SECTOR_SIZE, CHUNKSIZE):
- self.sdio.sdio_write_data_buffer(i, outbuf[i:i+CHUNKSIZE])
- params = self.sdio.sdio_write_data(
- SD_COMMANDS['WRITE_BLOCK'], offset)
- if (params['error'] != 0) or (params['write'] != SECTOR_SIZE):
- raise OSError(
- "flash_sdcard: Error writing to sector %d"% (sector,))
+ self.sdio.sdio_write_data_buffer(i, outbuf[i : i + CHUNKSIZE])
+ params = self.sdio.sdio_write_data(SD_COMMANDS["WRITE_BLOCK"], offset)
+ if (params["error"] != 0) or (params["write"] != SECTOR_SIZE):
+ raise OSError("flash_sdcard: Error writing to sector %d" % (sector,))
- status = self._send_command_with_response(
- 'SEND_STATUS', self.rca << 16)
+ status = self._send_command_with_response("SEND_STATUS", self.rca << 16)
if len(status) != 4:
- raise OSError("flash_sdcard: Failed to get status response"
- " after write: %s" % (repr(status),))
- if ((status[3]>>1) & 0x0F) != 0:
+ raise OSError(
+ "flash_sdcard: Failed to get status response"
+ " after write: %s" % (repr(status),)
+ )
+ if ((status[3] >> 1) & 0x0F) != 0:
# Bit 12:9 are not "0" (card is in idle)
- raise OSError("flash_sdcard: Write error."
- " Card is not in transfer state: 0x%02X"
- % (((status[3]>>1) & 0x0F)))
+ raise OSError(
+ "flash_sdcard: Write error."
+ " Card is not in transfer state: 0x%02X"
+ % (((status[3] >> 1) & 0x0F))
+ )
+
SDIO_WARNING = """
This board requires a manual reboot to complete the flash process.
@@ -1170,6 +1260,7 @@ power cycle is required. Please perform the power cycle now and then
rerun this utility with the 'check' option to verify flash.
"""
+
class MCUConnection:
def __init__(self, k_reactor, device, baud, board_cfg):
self.reactor = k_reactor
@@ -1194,7 +1285,7 @@ class MCUConnection:
self.reactor.register_callback(self._do_serial_connect)
curtime = self.reactor.monotonic()
while True:
- curtime = self.reactor.pause(curtime + 1.)
+ curtime = self.reactor.pause(curtime + 1.0)
output(".")
if self.connect_completion.test():
self.connected = self.connect_completion.wait()
@@ -1205,18 +1296,19 @@ class MCUConnection:
raise SPIFlashError("Unable to connect to MCU")
output_line("Connected")
msgparser = self._serial.get_msgparser()
- mcu_type = msgparser.get_constant('MCU')
- build_mcu_type = self.board_config['mcu']
+ mcu_type = msgparser.get_constant("MCU")
+ build_mcu_type = self.board_config["mcu"]
if mcu_type != build_mcu_type:
raise SPIFlashError(
"MCU Type mismatch: Build MCU = %s, Connected MCU = %s"
- % (build_mcu_type, mcu_type))
+ % (build_mcu_type, mcu_type)
+ )
self.enumerations = msgparser.get_enumerations()
self.raw_dictionary = msgparser.get_raw_data_dictionary()
self.proto_error = msgparser.error
def _do_serial_connect(self, eventtime):
- endtime = eventtime + 60.
+ endtime = eventtime + 60.0
while True:
try:
self._serial.connect_uart(self.serial_device, self.baud)
@@ -1228,7 +1320,7 @@ class MCUConnection:
return
output("Connection Error, retrying..")
self._serial.disconnect()
- self.reactor.pause(curtime + 2.)
+ self.reactor.pause(curtime + 2.0)
else:
break
self.connect_completion.complete(True)
@@ -1255,7 +1347,8 @@ class MCUConnection:
for response in GET_CFG_RESPONSES:
try:
get_cfg_cmd = mcu.CommandQueryWrapper(
- self._serial, GET_CFG_CMD, response)
+ self._serial, GET_CFG_CMD, response
+ )
break
except Exception as err:
# Raise an exception if we hit the end of the list.
@@ -1268,25 +1361,26 @@ class MCUConnection:
output("Checking Current MCU Configuration...")
params = self.get_mcu_config()
output_line("Done")
- if params['is_config'] or params['is_shutdown']:
- output_line("MCU needs restart: is_config=%d, is_shutdown=%d"
- % (params['is_config'], params['is_shutdown']))
+ if params["is_config"] or params["is_shutdown"]:
+ output_line(
+ "MCU needs restart: is_config=%d, is_shutdown=%d"
+ % (params["is_config"], params["is_shutdown"])
+ )
return True
return False
def _configure_mcu_spibus(self, printfunc=logging.info):
# TODO: add commands for buttons? Or perhaps an endstop? We
# just need to be able to query the status of the detect pin
- cs_pin = self.board_config['cs_pin'].upper()
- bus = self.board_config['spi_bus']
- bus_enums = self.enumerations.get(
- 'spi_bus', self.enumerations.get('bus'))
- pin_enums = self.enumerations.get('pin')
+ cs_pin = self.board_config["cs_pin"].upper()
+ bus = self.board_config["spi_bus"]
+ bus_enums = self.enumerations.get("spi_bus", self.enumerations.get("bus"))
+ pin_enums = self.enumerations.get("pin")
if bus == "swspi":
mcu_freq = self.clocksync.print_time_to_clock(1)
- pulse_ticks = mcu_freq//SD_SPI_SPEED
- cfgpins = self.board_config['spi_pins']
- pins = [p.strip().upper() for p in cfgpins.split(',') if p.strip()]
+ pulse_ticks = mcu_freq // SD_SPI_SPEED
+ cfgpins = self.board_config["spi_pins"]
+ pins = [p.strip().upper() for p in cfgpins.split(",") if p.strip()]
pin_err_msg = "Invalid Software SPI Pins: %s" % (cfgpins,)
if len(pins) != 3:
raise SPIFlashError(pin_err_msg)
@@ -1294,10 +1388,10 @@ class MCUConnection:
if p not in pin_enums:
raise SPIFlashError(pin_err_msg)
bus_cmds = [
- SW_SPI_BUS_CMDS[0] % (SPI_OID, pins[0], pins[1], pins[2],
- SPI_MODE, pulse_ticks),
- SW_SPI_BUS_CMDS[1] % (SPI_OID, pins[0], pins[1], pins[2],
- SPI_MODE, SD_SPI_SPEED)
+ SW_SPI_BUS_CMDS[0]
+ % (SPI_OID, pins[0], pins[1], pins[2], SPI_MODE, pulse_ticks),
+ SW_SPI_BUS_CMDS[1]
+ % (SPI_OID, pins[0], pins[1], pins[2], SPI_MODE, SD_SPI_SPEED),
]
else:
if bus not in bus_enums:
@@ -1307,7 +1401,9 @@ class MCUConnection:
]
if cs_pin not in pin_enums:
raise SPIFlashError("Invalid CS Pin: %s" % (cs_pin,))
- cfg_cmds = [ALLOC_OIDS_CMD % (1,),]
+ cfg_cmds = [
+ ALLOC_OIDS_CMD % (1,),
+ ]
self._serial.send(cfg_cmds[0])
spi_cfg_cmds = [
SPI_CFG_CMDS[0] % (SPI_OID, cs_pin, False),
@@ -1315,20 +1411,19 @@ class MCUConnection:
]
cfg_cmds.append(self._try_send_command(spi_cfg_cmds))
cfg_cmds.append(self._try_send_command(bus_cmds))
- config_crc = zlib.crc32('\n'.join(cfg_cmds).encode()) & 0xffffffff
+ config_crc = zlib.crc32("\n".join(cfg_cmds).encode()) & 0xFFFFFFFF
self._serial.send(FINALIZE_CFG_CMD % (config_crc,))
config = self.get_mcu_config()
if not config["is_config"] or config["is_shutdown"]:
raise MCUConfigError("Failed to configure MCU")
printfunc("Initializing SD Card and Mounting file system...")
self.fatfs = FatFS(self._serial)
- self.reactor.pause(self.reactor.monotonic() + .5)
+ self.reactor.pause(self.reactor.monotonic() + 0.5)
try:
self.fatfs.mount(printfunc)
except OSError:
logging.exception("SD Card Mount Failure")
- raise SPIFlashError(
- "Failed to Initialize SD Card. Is it inserted?")
+ raise SPIFlashError("Failed to Initialize SD Card. Is it inserted?")
def _try_send_command(self, cmd_list):
for cmd in cmd_list:
@@ -1341,10 +1436,9 @@ class MCUConnection:
return cmd
def _configure_mcu_sdiobus(self, printfunc=logging.info):
- bus = self.board_config['sdio_bus']
- bus_enums = self.enumerations.get(
- 'sdio_bus', self.enumerations.get('bus'))
- pin_enums = self.enumerations.get('pin')
+ bus = self.board_config["sdio_bus"]
+ bus_enums = self.enumerations.get("sdio_bus", self.enumerations.get("bus"))
+ pin_enums = self.enumerations.get("pin")
if bus not in bus_enums:
raise SPIFlashError("Invalid SDIO Bus: %s" % (bus,))
bus_cmd = SDIO_BUS_CMD % (SDIO_OID, bus)
@@ -1352,25 +1446,24 @@ class MCUConnection:
cfg_cmds = [ALLOC_OIDS_CMD % (1,), sdio_cfg_cmd, bus_cmd]
for cmd in cfg_cmds:
self._serial.send(cmd)
- config_crc = zlib.crc32('\n'.join(cfg_cmds).encode()) & 0xffffffff
+ config_crc = zlib.crc32("\n".join(cfg_cmds).encode()) & 0xFFFFFFFF
self._serial.send(FINALIZE_CFG_CMD % (config_crc,))
config = self.get_mcu_config()
if not config["is_config"] or config["is_shutdown"]:
raise MCUConfigError("Failed to configure MCU")
printfunc("Initializing SD Card and Mounting file system...")
- self.fatfs = FatFS(self._serial,spi=False)
- self.reactor.pause(self.reactor.monotonic() + .5)
+ self.fatfs = FatFS(self._serial, spi=False)
+ self.reactor.pause(self.reactor.monotonic() + 0.5)
try:
self.fatfs.mount(printfunc)
except OSError:
logging.exception("SD Card Mount Failure")
- raise SPIFlashError(
- "Failed to Initialize SD Card. Is it inserted?")
+ raise SPIFlashError("Failed to Initialize SD Card. Is it inserted?")
def configure_mcu(self, printfunc=logging.info):
- if 'spi_bus' in self.board_config:
+ if "spi_bus" in self.board_config:
self._configure_mcu_spibus(printfunc=printfunc)
- elif 'sdio_bus' in self.board_config:
+ elif "sdio_bus" in self.board_config:
self._configure_mcu_sdiobus(printfunc=printfunc)
else:
raise SPIFlashError("Unknown bus defined in board_defs.py.")
@@ -1379,10 +1472,10 @@ class MCUConnection:
output("Uploading Klipper Firmware to SD Card...")
input_sha = hashlib.sha1()
sd_sha = hashlib.sha1()
- klipper_bin_path = self.board_config['klipper_bin_path']
- fw_path = self.board_config.get('firmware_path', "firmware.bin")
+ klipper_bin_path = self.board_config["klipper_bin_path"]
+ fw_path = self.board_config.get("firmware_path", "firmware.bin")
try:
- with open(klipper_bin_path, 'rb') as local_f:
+ with open(klipper_bin_path, "rb") as local_f:
with self.fatfs.open_file(fw_path, "wb") as sd_f:
while True:
buf = local_f.read(4096)
@@ -1397,7 +1490,7 @@ class MCUConnection:
output("Validating Upload...")
try:
finfo = self.fatfs.get_file_info(fw_path)
- with self.fatfs.open_file(fw_path, 'r') as sd_f:
+ with self.fatfs.open_file(fw_path, "r") as sd_f:
while True:
buf = sd_f.read(4096)
if not buf:
@@ -1406,20 +1499,22 @@ class MCUConnection:
except Exception:
logging.exception("SD Card Download Error")
raise SPIFlashError("Error reading %s from SD" % (fw_path))
- sd_size = finfo.get('size', -1)
+ sd_size = finfo.get("size", -1)
input_chksm = input_sha.hexdigest().upper()
sd_chksm = sd_sha.hexdigest().upper()
if input_chksm != sd_chksm:
- raise SPIFlashError("Checksum Mismatch: Got '%s', expected '%s'"
- % (sd_chksm, input_chksm))
+ raise SPIFlashError(
+ "Checksum Mismatch: Got '%s', expected '%s'" % (sd_chksm, input_chksm)
+ )
output_line("Done")
output_line(
"Firmware Upload Complete: %s, Size: %d, Checksum (SHA1): %s"
- % (fw_path, sd_size, sd_chksm))
+ % (fw_path, sd_size, sd_chksm)
+ )
return sd_chksm
def verify_flash(self, req_chksm, old_dictionary, req_dictionary):
- if bool(self.board_config.get('skip_verify', False)):
+ if bool(self.board_config.get("skip_verify", False)):
output_line(SDIO_WARNING)
return
output("Verifying Flash...")
@@ -1429,10 +1524,14 @@ class MCUConnection:
# If we have a dictionary, check that it matches.
if req_dictionary:
if cur_dictionary != req_dictionary:
- raise SPIFlashError("Version Mismatch: Got '%s...', "
- "expected '%s...'"
- % (msgparser.get_version_info()[0],
- json.loads(req_dictionary)['version']))
+ raise SPIFlashError(
+ "Version Mismatch: Got '%s...', "
+ "expected '%s...'"
+ % (
+ msgparser.get_version_info()[0],
+ json.loads(req_dictionary)["version"],
+ )
+ )
output("Version matched...")
validation_passed = True
# Otherwise check that the MCU dictionary changed
@@ -1444,10 +1543,9 @@ class MCUConnection:
# If the version didn't change, look for current firmware to checksum
cur_fw_sha = None
if not validation_passed:
- cur_fw_path = self.board_config.get('current_firmware_path',
- "FIRMWARE.CUR")
+ cur_fw_path = self.board_config.get("current_firmware_path", "FIRMWARE.CUR")
try:
- with self.fatfs.open_file(cur_fw_path, 'r') as sd_f:
+ with self.fatfs.open_file(cur_fw_path, "r") as sd_f:
cur_fw_sha = hashlib.sha1()
while True:
buf = sd_f.read(4096)
@@ -1464,16 +1562,17 @@ class MCUConnection:
validation_passed = True
output("Checksum matched...")
else:
- raise SPIFlashError("Checksum Mismatch: Got '%s', "
- "expected '%s'"
- % (cur_fw_chksm, req_chksm))
+ raise SPIFlashError(
+ "Checksum Mismatch: Got '%s', "
+ "expected '%s'" % (cur_fw_chksm, req_chksm)
+ )
if not validation_passed:
raise SPIFlashError("Validation failure.")
output_line("Done")
# Remove firmware file if MCU bootloader failed to rename.
if cur_fw_sha is None:
try:
- fw_path = self.board_config.get('firmware_path', "firmware.bin")
+ fw_path = self.board_config.get("firmware_path", "firmware.bin")
self.fatfs.remove_item(fw_path)
output_line("Found and deleted %s after reset" % (fw_path,))
except Exception:
@@ -1481,17 +1580,19 @@ class MCUConnection:
output_line("Firmware Flash Successful")
output_line("Current Firmware: %s" % (msgparser.get_version_info()[0],))
+
class SPIFlash:
def __init__(self, args):
self.board_config = args
- if not os.path.exists(args['device']):
- raise SPIFlashError("No device found at '%s'" % (args['device'],))
- if not os.path.isfile(args['klipper_bin_path']):
- raise SPIFlashError("Unable to locate klipper binary at '%s'"
- % (args['klipper_bin_path'],))
- tty_name, dev_by_path = translate_serial_to_tty(args['device'])
+ if not os.path.exists(args["device"]):
+ raise SPIFlashError("No device found at '%s'" % (args["device"],))
+ if not os.path.isfile(args["klipper_bin_path"]):
+ raise SPIFlashError(
+ "Unable to locate klipper binary at '%s'" % (args["klipper_bin_path"],)
+ )
+ tty_name, dev_by_path = translate_serial_to_tty(args["device"])
self.device_path = dev_by_path
- self.baud_rate = args['baud']
+ self.baud_rate = args["baud"]
self.mcu_conn = None
self.firmware_checksum = None
self.task_complete = False
@@ -1499,24 +1600,26 @@ class SPIFlash:
self.need_verify = True
self.old_dictionary = None
self.new_dictionary = None
- if args['klipper_dict_path'] is not None:
+ if args["klipper_dict_path"] is not None:
try:
- with open(args['klipper_dict_path'], 'rb') as dict_f:
- self.new_dictionary = dict_f.read(32*1024)
+ with open(args["klipper_dict_path"], "rb") as dict_f:
+ self.new_dictionary = dict_f.read(32 * 1024)
except Exception:
- raise SPIFlashError("Missing or invalid dictionary at '%s'"
- % (args['klipper_dict_path'],))
+ raise SPIFlashError(
+ "Missing or invalid dictionary at '%s'"
+ % (args["klipper_dict_path"],)
+ )
def _wait_for_reconnect(self):
output("Waiting for device to reconnect...")
- time.sleep(1.)
+ time.sleep(1.0)
if os.path.exists(self.device_path):
# device is already available, this could be a UART
- time.sleep(2.)
+ time.sleep(2.0)
else:
wait_left = 30
while wait_left:
- time.sleep(1.)
+ time.sleep(1.0)
output(".")
if os.path.exists(self.device_path):
break
@@ -1568,16 +1671,18 @@ class SPIFlash:
if not self.mcu_conn.connected:
self.mcu_conn.connect()
self.mcu_conn.configure_mcu()
- self.mcu_conn.verify_flash(self.firmware_checksum, self.old_dictionary,
- self.new_dictionary)
+ self.mcu_conn.verify_flash(
+ self.firmware_checksum, self.old_dictionary, self.new_dictionary
+ )
self.mcu_conn.reset()
self.task_complete = True
def run_reactor_task(self, run_cb):
self.task_complete = False
k_reactor = reactor.Reactor()
- self.mcu_conn = MCUConnection(k_reactor, self.device_path,
- self.baud_rate, self.board_config)
+ self.mcu_conn = MCUConnection(
+ k_reactor, self.device_path, self.baud_rate, self.board_config
+ )
k_reactor.register_callback(run_cb)
try:
k_reactor.run()
@@ -1593,7 +1698,7 @@ class SPIFlash:
self.mcu_conn = k_reactor = None
def run(self):
- if not bool(self.board_config.get('verify_only', False)):
+ if not bool(self.board_config.get("verify_only", False)):
self.run_reactor_task(self.run_reset_upload)
self._wait_for_reconnect()
if self.need_upload:
@@ -1606,12 +1711,12 @@ class SPIFlash:
self._wait_for_reconnect()
self.run_reactor_task(self.run_verify)
+
def main():
- parser = argparse.ArgumentParser(
- description="SD Card Firmware Upload Utility")
+ parser = argparse.ArgumentParser(description="SD Card Firmware Upload Utility")
parser.add_argument(
- "-l", "--list", action="store_true",
- help="List Supported Boards")
+ "-l", "--list", action="store_true", help="List Supported Boards"
+ )
args = parser.parse_known_args()
if args[0].list:
blist = board_defs.list_boards()
@@ -1620,24 +1725,32 @@ def main():
output_line(board)
return
parser.add_argument(
- "-b", "--baud", metavar="<baud rate>", type=int,
- default=250000, help="Serial Baud Rate")
- parser.add_argument(
- "-v", "--verbose", action="store_true",
- help="Enable verbose output")
- parser.add_argument(
- "-d", "--dict_path", metavar="<klipper.dict>", type=str,
- default=None, help="Klipper firmware dictionary")
+ "-b",
+ "--baud",
+ metavar="<baud rate>",
+ type=int,
+ default=250000,
+ help="Serial Baud Rate",
+ )
parser.add_argument(
- "-c","--check", action="store_true",
- help="Perform flash check/verify only")
+ "-v", "--verbose", action="store_true", help="Enable verbose output"
+ )
parser.add_argument(
- "device", metavar="<device>", help="Device Serial Port")
+ "-d",
+ "--dict_path",
+ metavar="<klipper.dict>",
+ type=str,
+ default=None,
+ help="Klipper firmware dictionary",
+ )
parser.add_argument(
- "board", metavar="<board>", help="Board Type")
+ "-c", "--check", action="store_true", help="Perform flash check/verify only"
+ )
+ parser.add_argument("device", metavar="<device>", help="Device Serial Port")
+ parser.add_argument("board", metavar="<board>", help="Board Type")
parser.add_argument(
- "klipper_bin_path", metavar="<klipper.bin>",
- help="Klipper firmware binary")
+ "klipper_bin_path", metavar="<klipper.bin>", help="Klipper firmware binary"
+ )
args = parser.parse_args()
log_level = logging.DEBUG if args.verbose else logging.CRITICAL
logging.basicConfig(level=log_level)
@@ -1645,14 +1758,14 @@ def main():
if flash_args is None:
output_line("Unable to find definition for board: %s" % (args.board,))
sys.exit(-1)
- flash_args['device'] = args.device
- flash_args['baud'] = args.baud
- flash_args['klipper_bin_path'] = args.klipper_bin_path
- flash_args['klipper_dict_path'] = args.dict_path
- flash_args['verify_only'] = args.check
+ flash_args["device"] = args.device
+ flash_args["baud"] = args.baud
+ flash_args["klipper_bin_path"] = args.klipper_bin_path
+ flash_args["klipper_dict_path"] = args.dict_path
+ flash_args["verify_only"] = args.check
if args.check:
# override board_defs setting when doing verify-only:
- flash_args['skip_verify'] = False
+ flash_args["skip_verify"] = False
check_need_convert(args.board, flash_args)
fatfs_lib.check_fatfs_build(output)
try:
diff --git a/scripts/test_klippy.py b/scripts/test_klippy.py
index 8363ca0b..93ea2f34 100644
--- a/scripts/test_klippy.py
+++ b/scripts/test_klippy.py
@@ -14,9 +14,11 @@ TEMP_OUTPUT_FILE = "_test_output"
# Test cases
######################################################################
+
class error(Exception):
pass
+
class TestCase:
def __init__(self, fname, dictdir, tempdir, verbose, keepfiles):
self.fname = fname
@@ -24,22 +26,24 @@ class TestCase:
self.tempdir = tempdir
self.verbose = verbose
self.keepfiles = keepfiles
- def relpath(self, fname, rel='test'):
- if rel == 'dict':
+
+ def relpath(self, fname, rel="test"):
+ if rel == "dict":
reldir = self.dictdir
- elif rel == 'temp':
+ elif rel == "temp":
reldir = self.tempdir
else:
reldir = os.path.dirname(self.fname)
return os.path.join(reldir, fname)
+
def parse_test(self):
# Parse file into test cases
config_fname = gcode_fname = dict_fnames = None
should_fail = multi_tests = False
gcode = []
- f = open(self.fname, 'r')
+ f = open(self.fname, "r")
for line in f:
- cpos = line.find('#')
+ cpos = line.find("#")
if cpos >= 0:
line = line[:cpos]
parts = line.strip().split()
@@ -50,18 +54,21 @@ class TestCase:
# Multiple tests in same file
if not multi_tests:
multi_tests = True
- self.launch_test(config_fname, dict_fnames,
- gcode_fname, gcode, should_fail)
+ self.launch_test(
+ config_fname, dict_fnames, gcode_fname, gcode, should_fail
+ )
config_fname = self.relpath(parts[1])
if multi_tests:
- self.launch_test(config_fname, dict_fnames,
- gcode_fname, gcode, should_fail)
+ self.launch_test(
+ config_fname, dict_fnames, gcode_fname, gcode, should_fail
+ )
elif parts[0] == "DICTIONARY":
- dict_fnames = [self.relpath(parts[1], 'dict')]
+ dict_fnames = [self.relpath(parts[1], "dict")]
for mcu_dict in parts[2:]:
- mcu, fname = mcu_dict.split('=', 1)
- dict_fnames.append('%s=%s' % (
- mcu.strip(), self.relpath(fname.strip(), 'dict')))
+ mcu, fname = mcu_dict.split("=", 1)
+ dict_fnames.append(
+ "%s=%s" % (mcu.strip(), self.relpath(fname.strip(), "dict"))
+ )
elif parts[0] == "GCODE":
gcode_fname = self.relpath(parts[1])
elif parts[0] == "SHOULD_FAIL":
@@ -70,16 +77,15 @@ class TestCase:
gcode.append(line.strip())
f.close()
if not multi_tests:
- self.launch_test(config_fname, dict_fnames,
- gcode_fname, gcode, should_fail)
- def launch_test(self, config_fname, dict_fnames, gcode_fname, gcode,
- should_fail):
+ self.launch_test(config_fname, dict_fnames, gcode_fname, gcode, should_fail)
+
+ def launch_test(self, config_fname, dict_fnames, gcode_fname, gcode, should_fail):
gcode_is_temp = False
if gcode_fname is None:
- gcode_fname = self.relpath(TEMP_GCODE_FILE, 'temp')
+ gcode_fname = self.relpath(TEMP_GCODE_FILE, "temp")
gcode_is_temp = True
- f = open(gcode_fname, 'w')
- f.write('\n'.join(gcode + ['']))
+ f = open(gcode_fname, "w")
+ f.write("\n".join(gcode + [""]))
f.close()
elif gcode:
raise error("Can't specify both a gcode file and gcode commands")
@@ -88,14 +94,23 @@ class TestCase:
if dict_fnames is None:
raise error("data dictionary file not specified")
# Call klippy
- sys.stderr.write(" Starting %s (%s)\n" % (
- self.fname, os.path.basename(config_fname)))
- args = [ sys.executable, './klippy/klippy.py', config_fname,
- '-i', gcode_fname, '-o', TEMP_OUTPUT_FILE, '-v' ]
+ sys.stderr.write(
+ " Starting %s (%s)\n" % (self.fname, os.path.basename(config_fname))
+ )
+ args = [
+ sys.executable,
+ "./klippy/klippy.py",
+ config_fname,
+ "-i",
+ gcode_fname,
+ "-o",
+ TEMP_OUTPUT_FILE,
+ "-v",
+ ]
for df in dict_fnames:
- args += ['-d', df]
+ args += ["-d", df]
if not self.verbose:
- args += ['-l', TEMP_LOG_FILE]
+ args += ["-l", TEMP_LOG_FILE]
res = subprocess.call(args)
is_fail = (should_fail and not res) or (not should_fail and res)
if is_fail:
@@ -113,9 +128,10 @@ class TestCase:
if not self.verbose:
os.unlink(TEMP_LOG_FILE)
else:
- sys.stderr.write('\n')
+ sys.stderr.write("\n")
if gcode_is_temp:
os.unlink(gcode_fname)
+
def run(self):
try:
self.parse_test()
@@ -125,8 +141,9 @@ class TestCase:
logging.exception("Unhandled exception during test run")
return "internal error"
return "success"
+
def show_log(self):
- f = open(TEMP_LOG_FILE, 'r')
+ f = open(TEMP_LOG_FILE, "r")
data = f.read()
f.close()
sys.stdout.write(data)
@@ -136,18 +153,34 @@ class TestCase:
# Startup
######################################################################
+
def main():
# Parse args
usage = "%prog [options] <test cases>"
opts = optparse.OptionParser(usage)
- opts.add_option("-d", "--dictdir", dest="dictdir", default=".",
- help="directory for dictionary files")
- opts.add_option("-t", "--tempdir", dest="tempdir", default=".",
- help="directory for temporary files")
- opts.add_option("-k", action="store_true", dest="keepfiles",
- help="do not remove temporary files")
- opts.add_option("-v", action="store_true", dest="verbose",
- help="show all output from tests")
+ opts.add_option(
+ "-d",
+ "--dictdir",
+ dest="dictdir",
+ default=".",
+ help="directory for dictionary files",
+ )
+ opts.add_option(
+ "-t",
+ "--tempdir",
+ dest="tempdir",
+ default=".",
+ help="directory for temporary files",
+ )
+ opts.add_option(
+ "-k",
+ action="store_true",
+ dest="keepfiles",
+ help="do not remove temporary files",
+ )
+ opts.add_option(
+ "-v", action="store_true", dest="verbose", help="show all output from tests"
+ )
options, args = opts.parse_args()
if len(args) < 1:
opts.error("Incorrect number of arguments")
@@ -155,14 +188,16 @@ def main():
# Run each test
for fname in args:
- tc = TestCase(fname, options.dictdir, options.tempdir, options.verbose,
- options.keepfiles)
+ tc = TestCase(
+ fname, options.dictdir, options.tempdir, options.verbose, options.keepfiles
+ )
res = tc.run()
- if res != 'success':
+ if res != "success":
sys.stderr.write("\n\nTest case %s FAILED (%s)!\n\n" % (fname, res))
sys.exit(-1)
sys.stderr.write("\n All %d test cases passed\n" % (len(args),))
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/update_chitu.py b/scripts/update_chitu.py
index cf7fcfe9..62145371 100755
--- a/scripts/update_chitu.py
+++ b/scripts/update_chitu.py
@@ -11,14 +11,16 @@ import uuid
import sys
import hashlib
+
def calculate_crc(contents, seed):
- accumulating_xor_value = seed;
+ accumulating_xor_value = seed
for i in range(0, len(contents), 4):
- value = struct.unpack('<I', contents[ i : i + 4])[0]
+ value = struct.unpack("<I", contents[i : i + 4])[0]
accumulating_xor_value = accumulating_xor_value ^ value
return accumulating_xor_value
+
def xor_block(r0, r1, block_number, block_size, file_key):
# This is the loop counter
loop_counter = 0x0
@@ -27,17 +29,17 @@ def xor_block(r0, r1, block_number, block_size, file_key):
key_length = 0x18
# This is an initial seed
- xor_seed = 0x4bad
+ xor_seed = 0x4BAD
# This is the block counter
block_number = xor_seed * block_number
- #load the xor key from the file
- r7 = file_key
+ # load the xor key from the file
+ r7 = file_key
for loop_counter in range(0, block_size):
# meant to make sure different bits of the key are used.
- xor_seed = int(loop_counter/key_length)
+ xor_seed = int(loop_counter / key_length)
# IP is a scratch register / R12
ip = loop_counter - (key_length * xor_seed)
@@ -57,10 +59,10 @@ def xor_block(r0, r1, block_number, block_size, file_key):
# and then with IP
xor_seed = xor_seed ^ ip
- #Now store the byte back
+ # Now store the byte back
r1[loop_counter] = xor_seed & 0xFF
- #increment the loop_counter
+ # increment the loop_counter
loop_counter = loop_counter + 1
@@ -74,12 +76,12 @@ def encode_file(input, output_file, file_length):
print("Update UUID ", uid_value)
file_key = int(uid_value.hex[0:8], 16)
- xor_crc = 0xef3d4323;
+ xor_crc = 0xEF3D4323
# the input file is expected to be in chunks of 0x800
# so round the size
while len(input_file) % block_size != 0:
- input_file.extend(b'0x0')
+ input_file.extend(b"0x0")
# write the file header
output_file.write(struct.pack(">I", 0x443D2D3F))
@@ -88,15 +90,15 @@ def encode_file(input, output_file, file_length):
# write the file_key
output_file.write(struct.pack("<I", file_key))
- #TODO - how to enforce that the firmware aligns to block boundaries?
+ # TODO - how to enforce that the firmware aligns to block boundaries?
block_count = int(len(input_file) / block_size)
print("Block Count is ", block_count)
for block_number in range(0, block_count):
- block_offset = (block_number * block_size)
+ block_offset = block_number * block_size
block_end = block_offset + block_size
- block_array = bytearray(input_file[block_offset: block_end])
+ block_array = bytearray(input_file[block_offset:block_end])
xor_block(block_array, block_array, block_number, block_size, file_key)
- for n in range (0, block_size):
+ for n in range(0, block_size):
input_file[block_offset + n] = block_array[n]
# update the expected CRC value.
@@ -109,6 +111,7 @@ def encode_file(input, output_file, file_length):
output_file.write(input_file)
return
+
def main():
if len(sys.argv) != 3:
print("Usage: update_chitu <input_file> <output_file>")
@@ -132,5 +135,6 @@ def main():
print("Encoding complete.")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/update_mks_robin.py b/scripts/update_mks_robin.py
index fab9faa1..e4127648 100755
--- a/scripts/update_mks_robin.py
+++ b/scripts/update_mks_robin.py
@@ -7,12 +7,41 @@
import optparse
XOR_PATTERN = [
- 0xA3, 0xBD, 0xAD, 0x0D, 0x41, 0x11, 0xBB, 0x8D, 0xDC, 0x80,
- 0x2D, 0xD0, 0xD2, 0xC4, 0x9B, 0x1E, 0x26, 0xEB, 0xE3, 0x33,
- 0x4A, 0x15, 0xE4, 0x0A, 0xB3, 0xB1, 0x3C, 0x93, 0xBB, 0xAF,
- 0xF7, 0x3E
+ 0xA3,
+ 0xBD,
+ 0xAD,
+ 0x0D,
+ 0x41,
+ 0x11,
+ 0xBB,
+ 0x8D,
+ 0xDC,
+ 0x80,
+ 0x2D,
+ 0xD0,
+ 0xD2,
+ 0xC4,
+ 0x9B,
+ 0x1E,
+ 0x26,
+ 0xEB,
+ 0xE3,
+ 0x33,
+ 0x4A,
+ 0x15,
+ 0xE4,
+ 0x0A,
+ 0xB3,
+ 0xB1,
+ 0x3C,
+ 0x93,
+ 0xBB,
+ 0xAF,
+ 0xF7,
+ 0x3E,
]
+
def main():
# Parse command-line arguments
usage = "%prog <input_file> <output_file>"
@@ -34,5 +63,6 @@ def main():
f.write(firmware)
f.close()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/scripts/whconsole.py b/scripts/whconsole.py
index 5e76b3bc..59e726f1 100755
--- a/scripts/whconsole.py
+++ b/scripts/whconsole.py
@@ -6,10 +6,11 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, optparse, socket, fcntl, select, json, errno, time
+
# Set a file-descriptor as non-blocking
def set_nonblock(fd):
- fcntl.fcntl(fd, fcntl.F_SETFL
- , fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+
def webhook_socket_create(uds_filename):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -22,14 +23,16 @@ def webhook_socket_create(uds_filename):
if e.errno == errno.ECONNREFUSED:
time.sleep(0.1)
continue
- sys.stderr.write("Unable to connect socket %s [%d,%s]\n"
- % (uds_filename, e.errno,
- errno.errorcode[e.errno]))
+ sys.stderr.write(
+ "Unable to connect socket %s [%d,%s]\n"
+ % (uds_filename, e.errno, errno.errorcode[e.errno])
+ )
sys.exit(-1)
break
sys.stderr.write("Connection.\n")
return sock
+
class KeyboardReader:
def __init__(self, uds_filename):
self.kbd_fd = sys.stdin.fileno()
@@ -39,42 +42,46 @@ class KeyboardReader:
self.poll.register(sys.stdin, select.POLLIN | select.POLLHUP)
self.poll.register(self.webhook_socket, select.POLLIN | select.POLLHUP)
self.kbd_data = self.socket_data = b""
+
def process_socket(self):
data = self.webhook_socket.recv(4096)
if not data:
sys.stderr.write("Socket closed\n")
sys.exit(0)
- parts = data.split(b'\x03')
+ parts = data.split(b"\x03")
parts[0] = self.socket_data + parts[0]
self.socket_data = parts.pop()
for line in parts:
sys.stdout.write("GOT: %s\n" % (line,))
+
def process_kbd(self):
data = os.read(self.kbd_fd, 4096)
- parts = data.split(b'\n')
+ parts = data.split(b"\n")
parts[0] = self.kbd_data + parts[0]
self.kbd_data = parts.pop()
for line in parts:
line = line.strip()
- if not line or line.startswith(b'#'):
+ if not line or line.startswith(b"#"):
continue
try:
m = json.loads(line)
except:
sys.stderr.write("ERROR: Unable to parse line\n")
continue
- cm = json.dumps(m, separators=(',', ':'))
+ cm = json.dumps(m, separators=(",", ":"))
sys.stdout.write("SEND: %s\n" % (cm,))
self.webhook_socket.send(cm.encode() + b"\x03")
+
def run(self):
while 1:
- res = self.poll.poll(1000.)
+ res = self.poll.poll(1000.0)
for fd, event in res:
if fd == self.kbd_fd:
self.process_kbd()
else:
self.process_socket()
+
def main():
usage = "%prog [options] <socket filename>"
opts = optparse.OptionParser(usage)
@@ -85,5 +92,6 @@ def main():
ml = KeyboardReader(args[0])
ml.run()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()