diff --git a/klippy/__init__.py b/klippy/__init__.py new file mode 100644 index 000000000..eb6b9c2d1 --- /dev/null +++ b/klippy/__init__.py @@ -0,0 +1,3 @@ +APP_NAME = "Kalico" + +from .printer import * # noqa: E402, F403 diff --git a/klippy/__main__.py b/klippy/__main__.py new file mode 100644 index 000000000..4d96bc05e --- /dev/null +++ b/klippy/__main__.py @@ -0,0 +1,3 @@ +from .printer import main + +main() diff --git a/klippy/compat.py b/klippy/compat.py new file mode 100644 index 000000000..ea6022c57 --- /dev/null +++ b/klippy/compat.py @@ -0,0 +1,20 @@ +import sys + + +def hotpatch_modules(): + """ + This is a compatibility shim for legacy external modules + to fix + Redirect legacy `import x` to `import klippy.x` + + """ + + for module_name, module in list(sys.modules.items()): + if not module_name.startswith("klippy."): + continue + + hotpatched_name = module_name.removeprefix("klippy.") + if hotpatched_name in sys.modules: + continue + + sys.modules[hotpatched_name] = module diff --git a/klippy/configfile.py b/klippy/configfile.py index e157c9bd8..a87c81113 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -3,8 +3,10 @@ # Copyright (C) 2016-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, glob, re, time, logging, configparser, io, mathutil -from extras.danger_options import get_danger_options +import sys, os, glob, re, time, logging, configparser, io +from .extras.danger_options import get_danger_options +from . import mathutil + error = configparser.Error diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py index 2637c9d85..456e09bc2 100644 --- a/klippy/extras/adc_temperature.py +++ b/klippy/extras/adc_temperature.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, bisect -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # Interface between MCU adc and heater temperature callbacks diff --git a/klippy/extras/aht10.py b/klippy/extras/aht10.py index 16d17a343..9b0241884 100644 --- a/klippy/extras/aht10.py +++ b/klippy/extras/aht10.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # Compatible Sensors: diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 38b3233b8..362d6ebd2 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math, json, collections from . import probe -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options PROFILE_VERSION = 1 PROFILE_OPTIONS = { diff --git a/klippy/extras/bed_tilt.py b/klippy/extras/bed_tilt.py index 2b09cb35a..556cc3657 100644 --- a/klippy/extras/bed_tilt.py +++ b/klippy/extras/bed_tilt.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import mathutil +from klippy import mathutil from . import probe diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index b765feba5..0888739ac 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging from . import probe -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options SIGNAL_PERIOD = 0.020 MIN_CMD_TIME = 5 * SIGNAL_PERIOD diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index ebd99d1ff..0f57c7c39 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options REPORT_TIME = 0.8 BME280_CHIP_ADDR = 0x76 diff --git a/klippy/extras/bus.py b/klippy/extras/bus.py index cb19776cf..f0797a324 100644 --- a/klippy/extras/bus.py +++ b/klippy/extras/bus.py @@ -3,7 +3,7 @@ # Copyright (C) 2018,2019 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import mcu +from klippy import mcu def resolve_bus_name(mcu, param, bus): diff --git a/klippy/extras/delta_calibrate.py b/klippy/extras/delta_calibrate.py index 4eb4b587e..d43c76c6b 100644 --- a/klippy/extras/delta_calibrate.py +++ b/klippy/extras/delta_calibrate.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging import math -import mathutil +from klippy import mathutil from . import probe # A "stable position" is a 3-tuple containing the number of steps diff --git a/klippy/extras/ds18b20.py b/klippy/extras/ds18b20.py index 64b002e2d..ab111ab9f 100644 --- a/klippy/extras/ds18b20.py +++ b/klippy/extras/ds18b20.py @@ -3,9 +3,10 @@ # Copyright (C) 2020 Alan Lord # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, mcu +import logging -from extras.danger_options import get_danger_options +from klippy import mcu +from .danger_options import get_danger_options DS18_REPORT_TIME = 3.0 # Temperature can be sampled at any time but conversion time is ~750ms, so diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py index 221b9b99e..a7bf5fbc1 100644 --- a/klippy/extras/endstop_phase.py +++ b/klippy/extras/endstop_phase.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper +from klippy import stepper TRINAMIC_DRIVERS = [ "tmc2130", diff --git a/klippy/extras/extruder_stepper.py b/klippy/extras/extruder_stepper.py index d78028ad9..6bcec529b 100644 --- a/klippy/extras/extruder_stepper.py +++ b/klippy/extras/extruder_stepper.py @@ -3,7 +3,7 @@ # Copyright (C) 2019 Simo Apell # # This file may be distributed under the terms of the GNU GPLv3 license. -from kinematics import extruder +from klippy.kinematics import extruder class PrinterExtruderStepper: diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 9adee13b4..d224df556 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import chelper +from klippy import chelper BUZZ_DISTANCE = 1.0 BUZZ_VELOCITY = BUZZ_DISTANCE / 0.250 diff --git a/klippy/extras/gcode_macro.py b/klippy/extras/gcode_macro.py index f3ccd24e8..700443203 100644 --- a/klippy/extras/gcode_macro.py +++ b/klippy/extras/gcode_macro.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import traceback, logging, ast, copy, json, threading import jinja2, math -import configfile +from klippy import configfile PYTHON_SCRIPT_PREFIX = "!" diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index d25158bb1..a32341929 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import math import logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options # HOMING_START_DELAY = 0.001 # ENDSTOP_SAMPLE_TIME = 0.000015 diff --git a/klippy/extras/htu21d.py b/klippy/extras/htu21d.py index 33252ce80..c378be2d4 100644 --- a/klippy/extras/htu21d.py +++ b/klippy/extras/htu21d.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # NOTE: The implementation requires write support of length 0 diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py index 99179895e..9672bf7ea 100644 --- a/klippy/extras/input_shaper.py +++ b/klippy/extras/input_shaper.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import collections -import chelper +from klippy import chelper from . import shaper_defs diff --git a/klippy/extras/lm75.py b/klippy/extras/lm75.py index de0eab70f..2e98c9749 100644 --- a/klippy/extras/lm75.py +++ b/klippy/extras/lm75.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options LM75_CHIP_ADDR = 0x48 LM75_I2C_SPEED = 100000 diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index c667ecf9c..821020e2f 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -3,7 +3,7 @@ # Copyright (C) 2019-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper, chelper +from klippy import stepper, chelper from . import force_move diff --git a/klippy/extras/motion_report.py b/klippy/extras/motion_report.py index 38a9c3169..e6080b5cf 100644 --- a/klippy/extras/motion_report.py +++ b/klippy/extras/motion_report.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import chelper +from klippy import chelper from . import bulk_sensor diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index fc4bceeb9..e25ae1177 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -4,8 +4,8 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import pins import math +from klippy import pins from . import manual_probe HINT_TIMEOUT = """ diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 2e9bed3c9..5d78bea0d 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, bisect -import mcu +from klippy import mcu from . import ldc1612, probe, manual_probe diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index c9eb69d48..c813b773d 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -3,7 +3,7 @@ # Copyright (C) 2017-2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import chelper +from klippy import chelper MAX_SCHEDULE_TIME = 5.0 diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py index 6d0ba4e59..0cccf07e5 100644 --- a/klippy/extras/replicape.py +++ b/klippy/extras/replicape.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, os -import pins, mcu +from klippy import pins, mcu from . import bus REPLICAPE_MAX_CURRENT = 3.84 diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py index 9e23692a6..e2f66b737 100644 --- a/klippy/extras/shaper_calibrate.py +++ b/klippy/extras/shaper_calibrate.py @@ -9,7 +9,7 @@ import multiprocessing import traceback -shaper_defs = importlib.import_module(".shaper_defs", "extras") +from . import shaper_defs MIN_FREQ = 5.0 MAX_FREQ = 200.0 diff --git a/klippy/extras/sht3x.py b/klippy/extras/sht3x.py index 69b1120f6..a15db8713 100644 --- a/klippy/extras/sht3x.py +++ b/klippy/extras/sht3x.py @@ -7,7 +7,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # Compatible Sensors: diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py index 53d0fc934..6ef75a555 100644 --- a/klippy/extras/spi_temperature.py +++ b/klippy/extras/spi_temperature.py @@ -7,7 +7,7 @@ import math, logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # SensorBase diff --git a/klippy/extras/statistics.py b/klippy/extras/statistics.py index 4d7bb9d69..6f704b78b 100644 --- a/klippy/extras/statistics.py +++ b/klippy/extras/statistics.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import os, time, logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options class PrinterSysStats: diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py index 4bd52fc37..9bd16c29b 100644 --- a/klippy/extras/sx1509.py +++ b/klippy/extras/sx1509.py @@ -3,7 +3,7 @@ # Copyright (C) 2018 Florian Heilmann # # This file may be distributed under the terms of the GNU GPLv3 license. -import pins +from klippy import pins from . import bus # Word registers diff --git a/klippy/extras/telemetry.py b/klippy/extras/telemetry.py index f5e730df7..ee32b88f4 100644 --- a/klippy/extras/telemetry.py +++ b/klippy/extras/telemetry.py @@ -10,6 +10,7 @@ import pathlib import platform import sys +import shlex import threading import urllib.request import uuid @@ -129,9 +130,7 @@ def cmd_TELEMETRY_EXAMPLE(self, gcmd): with filename.open("w", encoding="utf-8") as fp: json.dump(data, fp, indent=2) - gcmd.respond_info( - f"Example telemetry saved to {filename.relative_to(pathlib.Path.home())}" - ) + gcmd.respond_info(f"Example telemetry saved to {filename}") def _get_machine_id(self): """ @@ -198,26 +197,51 @@ def _collect_platform(self): { "machine": "x86_64", - "os_release": { - "NAME": "Debian GNU/Linux", - "ID": "debian", - "PRETTY_NAME": "Debian GNU/Linux trixie/sid", - "VERSION_CODENAME": "trixie", - "HOME_URL": "https://www.debian.org/", - "SUPPORT_URL": "https://www.debian.org/support", - "BUG_REPORT_URL": "https://bugs.debian.org/" - }, + "os_release": { ... }, "version": "#1 SMP PREEMPT_DYNAMIC Debian 6.12~rc6-1~exp1 (2024-11-10)", "python": "3.12.7 (main, Nov 8 2024, 17:55:36) [GCC 14.2.0]" } """ return { "machine": platform.machine(), - "os_release": platform.freedesktop_os_release(), + "os_release": self._collect_os_release(), "version": platform.version(), "python": sys.version, } + def _collect_os_release(self): + """ + Collect the freedesktop OS-RELEASE information. + See also `platform.freedesktop_os_release()` (available in Python 3.10+) + + { + "NAME": "Debian GNU/Linux", + "ID": "debian", + "PRETTY_NAME": "Debian GNU/Linux trixie/sid", + "VERSION_CODENAME": "trixie", + "HOME_URL": "https://www.debian.org/", + "SUPPORT_URL": "https://www.debian.org/support", + "BUG_REPORT_URL": "https://bugs.debian.org/" + } + """ + paths = [ + pathlib.Path("/etc/os-release"), + pathlib.Path("/usr/lib/os-release"), + ] + path = next(filter(pathlib.Path.exists, paths), None) + if not path: + return + + result = {} + with path.open("r") as fp: + for line in fp: + if "=" not in line: + continue + key, value = line.split("=", maxsplit=1) + result[key] = shlex.split(value)[0] + + return result + def _collect_printer_objects(self): """ Collect a list of all enabled objects in the current Kalico runtime diff --git a/klippy/extras/temperature_combined.py b/klippy/extras/temperature_combined.py index 3d9549751..97417b5d1 100644 --- a/klippy/extras/temperature_combined.py +++ b/klippy/extras/temperature_combined.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options REPORT_TIME = 0.300 diff --git a/klippy/extras/temperature_host.py b/klippy/extras/temperature_host.py index 93c3359eb..764a8cec1 100644 --- a/klippy/extras/temperature_host.py +++ b/klippy/extras/temperature_host.py @@ -6,7 +6,7 @@ import logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options HOST_REPORT_TIME = 1.0 RPI_PROC_TEMP_FILE = "/sys/class/thermal/thermal_zone0/temp" diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index 32c3c7853..9167ff79d 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options SAMPLE_TIME = 0.001 SAMPLE_COUNT = 8 diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 266e6e1e7..ff699060b 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, collections -import stepper +from klippy import stepper ###################################################################### diff --git a/klippy/extras/tools_calibrate.py b/klippy/extras/tools_calibrate.py index b2bea4bc9..490b9ba6b 100644 --- a/klippy/extras/tools_calibrate.py +++ b/klippy/extras/tools_calibrate.py @@ -5,9 +5,10 @@ # Sourced from https://github.com/viesturz/klipper-toolchanger/blob/main/klipper/extras/tools_calibrate.py import collections -import logging -import pins import enum +import logging + +from klippy import pins class Axis(enum.IntEnum): diff --git a/klippy/extras/trad_rack.py b/klippy/extras/trad_rack.py index c11bbd3e3..1f4426bd4 100644 --- a/klippy/extras/trad_rack.py +++ b/klippy/extras/trad_rack.py @@ -6,10 +6,11 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math, os, time from collections import deque -from extras.homing import Homing, HomingMove -from gcode import CommandError -from stepper import LookupMultiRail -import chelper, toolhead, kinematics.extruder +from .homing import Homing, HomingMove +from .. import chelper, toolhead +from ..gcode import CommandError +from ..stepper import LookupMultiRail +from ..kinematics import extruder SERVO_NAME = "servo tr_servo" SELECTOR_STEPPER_NAME = "stepper_tr_selector" @@ -2389,7 +2390,7 @@ def __init__(self, config, buffer_pull_speed, is_extruder_synced): # Create kinematic class gcode = self.printer.lookup_object("gcode") self.Coord = gcode.Coord - self.extruder = kinematics.extruder.DummyExtruder(self.printer) + self.extruder = extruder.DummyExtruder(self.printer) try: self.kin = TradRackKinematics(self, config, is_extruder_synced) except config.error as e: diff --git a/klippy/extras/z_calibration.py b/klippy/extras/z_calibration.py index f6f838bef..d9bbaa0fa 100644 --- a/klippy/extras/z_calibration.py +++ b/klippy/extras/z_calibration.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -from mcu import MCU_endstop +from klippy.mcu import MCU_endstop class ZCalibrationHelper: diff --git a/klippy/extras/z_tilt.py b/klippy/extras/z_tilt.py index f63de211b..f07af9c9e 100644 --- a/klippy/extras/z_tilt.py +++ b/klippy/extras/z_tilt.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import mathutil +from klippy import mathutil from . import probe diff --git a/klippy/extras/z_tilt_ng.py b/klippy/extras/z_tilt_ng.py index d806a738b..5bbd9422b 100644 --- a/klippy/extras/z_tilt_ng.py +++ b/klippy/extras/z_tilt_ng.py @@ -4,8 +4,8 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import mathutil import numpy as np +from klippy import mathutil from . import probe diff --git a/klippy/gcode.py b/klippy/gcode.py index 183eb01d6..f6c84f213 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import os, re, logging, collections, shlex -import mathutil +from . import mathutil class CommandError(Exception): @@ -441,7 +441,7 @@ def cmd_M112(self, gcmd): def cmd_M115(self, gcmd): # Get Firmware Version and Capabilities software_version = self.printer.get_start_args().get("software_version") - kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version} + kw = {"FIRMWARE_NAME": "Kalico", "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: diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 24577ab9f..b08add5ef 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -3,7 +3,7 @@ # Copyright (C) 2016-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper from . import idex_modes diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index 25f1e7b20..4841d481f 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -3,7 +3,7 @@ # Copyright (C) 2017-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper class CoreXYKinematics: diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index e67d7b354..5fb383146 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -3,7 +3,7 @@ # Copyright (C) 2020 Maks Zolin # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper class CoreXZKinematics: diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index e28fd0a16..0d6f0602b 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper, mathutil +from klippy import stepper, mathutil # Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO SLOW_RATIO = 3.0 diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index 7a6c5feb7..e51c11239 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper +from klippy import stepper # Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO SLOW_RATIO = 3.0 diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 9445a77dd..ab955aa01 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper, chelper +from klippy import stepper, chelper class ExtruderStepper: diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 2b927f002..64da79c68 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -3,7 +3,7 @@ # Copyright (C) 2021 Fabrice Gallet # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper from . import idex_modes diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index b9993f545..819cfee6e 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -3,7 +3,7 @@ # Copyright (C) 2021 Fabrice Gallet # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper from . import idex_modes diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index ab317e4cb..1cdf34037 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math -import chelper +from klippy import chelper INACTIVE = "INACTIVE" PRIMARY = "PRIMARY" diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index c5fd7af55..27e55e076 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math -import stepper +from klippy import stepper class PolarKinematics: diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 0e8da2411..950b3377a 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper, mathutil, chelper +from klippy import stepper, mathutil, chelper class RotaryDeltaKinematics: diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 75a9f82db..fe3202d9a 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -3,7 +3,7 @@ # Copyright (C) 2018-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper, mathutil +from klippy import stepper, mathutil class WinchKinematics: diff --git a/klippy/klippy.py b/klippy/klippy.py index be027f679..70b6f9811 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -1,601 +1,10 @@ -#!/usr/bin/env python2 -# Main code for host side printer firmware -# -# Copyright (C) 2016-2020 Kevin O'Connor -# -# This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, gc, optparse, logging, time, collections, importlib, importlib.util -import util, reactor, queuelogger, msgproto -import gcode, configfile, pins, mcu, toolhead, webhooks -from extras.danger_options import get_danger_options +#!/usr/bin/env python3 -APP_NAME = "Kalico" +import sys, pathlib -message_ready = "Printer is ready" - -message_startup = """ -Printer is not ready -The klippy host software is attempting to connect. Please -retry in a few moments. -""" - -message_restart = """ -Once the underlying issue is corrected, use the "RESTART" -command to reload the config and restart the host software. -Printer is halted -""" - -message_protocol_error = """MCU Protocol error""" - -message_protocol_error1 = """ -This is frequently caused by running an older version of the -firmware on the MCU(s). Fix by recompiling and flashing the -firmware. -""" - -message_protocol_error2 = """ -Once the underlying issue is corrected, use the "RESTART" -command to reload the config and restart the host software. -""" - -message_mcu_connect_error = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Error configuring printer -""" - -message_shutdown = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Printer is shutdown -""" - - -class WaitInterruption(gcode.CommandError): - pass - - -class Printer: - config_error = configfile.error - command_error = gcode.CommandError - - def __init__(self, main_reactor, bglogger, start_args): - if sys.version_info[0] < 3: - logging.error("Kalico requires Python 3") - sys.exit(1) - - self.bglogger = bglogger - self.start_args = start_args - self.reactor = main_reactor - self.reactor.register_callback(self._connect) - self.state_message = message_startup - self.in_shutdown_state = False - self.run_result = None - self.event_handlers = {} - self.objects = collections.OrderedDict() - # 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" - elif self.state_message == message_startup: - category = "startup" - elif self.in_shutdown_state: - category = "shutdown" - 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") - - def add_object(self, name, obj): - if name in self.objects: - 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) - ] - 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] - extras_py_name = os.path.join( - os.path.dirname(__file__), "extras", module_name + ".py" - ) - extras_py_dirname = os.path.join( - os.path.dirname(__file__), "extras", module_name, "__init__.py" - ) - - plugins_py_dirname = os.path.join( - os.path.dirname(__file__), "plugins", module_name, "__init__.py" - ) - plugins_py_name = os.path.join( - os.path.dirname(__file__), "plugins", module_name + ".py" - ) - - found_in_extras = os.path.exists(extras_py_name) or os.path.exists( - extras_py_dirname - ) - found_in_plugins = os.path.exists(plugins_py_name) - found_in_plugins_dir = os.path.exists(plugins_py_dirname) - - if not any([found_in_extras, found_in_plugins, found_in_plugins_dir]): - if default is not configfile.sentinel: - return default - raise self.config_error("Unable to load module '%s'" % (section,)) - - if ( - found_in_extras - and (found_in_plugins or found_in_plugins_dir) - and not get_danger_options().allow_plugin_override - ): - raise self.config_error( - "Module '%s' found in both extras and plugins!" % (section,) - ) - - if found_in_plugins: - mod_spec = importlib.util.spec_from_file_location( - "extras." + module_name, plugins_py_name - ) - mod = importlib.util.module_from_spec(mod_spec) - mod_spec.loader.exec_module(mod) - elif found_in_plugins_dir: - mod_spec = importlib.util.spec_from_file_location( - "plugins." + module_name, plugins_py_dirname - ) - mod = importlib.util.module_from_spec(mod_spec) - mod_spec.loader.exec_module(mod) - else: - mod = importlib.import_module("extras." + module_name) - - init_func = "load_config" - if len(module_parts) > 1: - init_func = "load_config_prefix" - init_func = getattr(mod, init_func, None) - if init_func is None: - if default is not configfile.sentinel: - return default - 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) - config = pconfig.read_main_config() - self.load_object(config, "danger_options", None) - if ( - self.bglogger is not None - and get_danger_options().log_config_file_at_startup - ): - 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(""): - self.load_object(config, section_config.get_name(), None) - # kalico on-by-default extras - for section_config in [ - "force_move", - "respond", - "exclude_object", - "telemetry", - ]: - self.load_object(config, section_config, None) - for m in [toolhead]: - m.add_printer_objects(config) - # Validate that there are no undefined parameters in the config file - error_on_unused = get_danger_options().error_on_unused_config_options - pconfig.check_unused_options(config, error_on_unused) - - def _build_protocol_error_message(self, e): - host_version = self.start_args["software_version"] - - msg_update = [] - msg_updated = [] - - for mcu_name, mcu_obj in self.lookup_objects("mcu"): - try: - mcu_version = mcu_obj.get_status()["mcu_version"] - except: - logging.exception("Unable to retrieve mcu_version from mcu_obj") - continue - - if mcu_version != host_version: - msg_update.append( - "%s: Current version %s" - % ( - mcu_name.split()[-1], - mcu_obj.get_status()["mcu_version"], - ) - ) - else: - msg_updated.append( - "%s: Current version %s" - % ( - mcu_name.split()[-1], - mcu_obj.get_status()["mcu_version"], - ) - ) - - if not len(msg_updated): - msg_updated.append("") - - version_msg = [ - "\nYour Kalico version is: %s\n" % host_version, - "MCU(s) which should be updated:", - "\n%s\n" % "\n".join(msg_update), - "Up-to-date MCU(s):", - "\n%s\n" % "\n".join(msg_updated), - ] - - msg = [ - message_protocol_error, - "", - " ".join(message_protocol_error1.splitlines())[1:], - "\n".join(version_msg), - " ".join(message_protocol_error2.splitlines())[1:], - "", - str(e), - ] - - return "\n".join(msg) - - def _connect(self, eventtime): - try: - self._read_config() - self.send_event("klippy:mcu_identify") - for cb in self.event_handlers.get("klippy:connect", []): - if self.state_message is not message_startup: - return - cb() - except (self.config_error, pins.error) as e: - logging.exception("Config error") - self._set_state("%s\n%s" % (str(e), message_restart)) - return - except msgproto.error as e: - logging.exception("Protocol error") - self._set_state(self._build_protocol_error_message(e)) - util.dump_mcu_build() - return - except mcu.error as e: - logging.exception("MCU error during connect") - self._set_state("%s%s" % (str(e), message_mcu_connect_error)) - util.dump_mcu_build() - 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, - ) - ) - return - try: - self._set_state(message_ready) - for cb in self.event_handlers.get("klippy:ready", []): - if self.state_message is not message_ready: - return - cb() - except Exception as e: - logging.exception("Unhandled exception during ready callback") - 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, - ) - # Enter main reactor loop - try: - self.reactor.run() - except: - msg = "Unhandled exception during run" - logging.exception(msg) - # Exception from a reactor callback - try to shutdown - try: - self.reactor.register_callback( - (lambda e: self.invoke_shutdown(msg)) - ) - self.reactor.run() - except: - logging.exception("Repeat unhandled exception during run") - # Another exception - try to exit - self.run_result = "error_exit" - # Check restart flags - run_result = self.run_result - try: - 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): - if self.in_shutdown_state: - return - logging.error("Transition to shutdown state: %s", msg) - self.in_shutdown_state = True - self._set_state("%s%s" % (msg, message_shutdown)) - for cb in self.event_handlers.get("klippy:shutdown", []): - try: - cb() - except: - logging.exception("Exception during shutdown handler") - logging.info( - "Reactor garbage collection: %s", self.reactor.get_gc_stats() - ) - - def invoke_async_shutdown(self, msg): - self.reactor.register_async_callback( - (lambda e: self.invoke_shutdown(msg)) - ) - - 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 - self.reactor.end() - - wait_interrupted = WaitInterruption - - def wait_while(self, condition_cb, error_on_cancel=True, interval=1.0): - """ - receives a callback - waits until callback returns False - (or is interrupted, or printer shuts down) - """ - gcode = self.lookup_object("gcode") - counter = gcode.get_interrupt_counter() - eventtime = self.reactor.monotonic() - while condition_cb(eventtime): - if self.is_shutdown() or counter != gcode.get_interrupt_counter(): - if error_on_cancel: - raise WaitInterruption("Command interrupted") - else: - return - eventtime = self.reactor.pause(eventtime + interval) - - -###################################################################### -# Startup -###################################################################### - - -def import_test(): - # Import all optional modules (used as a build test) - from extras import danger_options - from unittest import mock - - danger_options.DANGER_OPTIONS = mock.Mock() - dname = os.path.dirname(__file__) - for mname in ["extras", "kinematics"]: - for fname in os.listdir(os.path.join(dname, mname)): - if fname.endswith(".py") and fname != "__init__.py": - module_name = fname[:-3] - else: - 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) - sys.exit(0) - - -def arg_dictionary(option, opt_str, value, parser): - key, fname = "dictionary", value - 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] " - 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( - "--rotate-log-at-restart", - action="store_true", - help="rotate the log file at every restart", - ) - 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", - } - - 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() - else: - start_args["gcode_fd"] = util.create_pty(options.inputtty) - if options.debugoutput: - start_args["debugoutput"] = options.debugoutput - start_args.update(options.dictionary) - bglogger = None - if options.logfile: - start_args["log_file"] = options.logfile - bglogger = queuelogger.setup_bg_logging( - filename=options.logfile, - debuglevel=debuglevel, - rotate_log_at_restart=options.rotate_log_at_restart, - ) - if options.rotate_log_at_restart: - bglogger.doRollover() - else: - logging.getLogger().setLevel(debuglevel) - logging.info("=======================") - logging.info("Starting Klippy...") - git_info = util.get_git_version() - git_vers = git_info["version"] - - extra_git_desc = "" - 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["git_branch"] = git_info["branch"] - start_args["git_remote"] = git_info["remote"] - start_args["cpu_info"] = util.get_cpu_info() - if bglogger is not None: - versions = "\n".join( - [ - f"Args: {sys.argv}", - f"App Name: {APP_NAME}", - f"Git version: {repr(start_args['software_version'])}{extra_git_desc}", - f"CPU: {start_args['cpu_info']}", - f"Python: {repr(sys.version)}", - ] - ) - logging.info(versions) - elif not options.debugoutput: - logging.warning( - "No log file specified!" " Severe timing issues may result!" - ) - gc.disable() - - # Start Printer() class - while True: - if bglogger is not None: - bglogger.clear_rollover_info() - 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"]: - break - time.sleep(1.0) - main_reactor.finalize() - main_reactor = printer = None - logging.info("Restarting printer") - start_args["start_reason"] = res - if options.rotate_log_at_restart and bglogger is not None: - bglogger.doRollover() - - if bglogger is not None: - bglogger.stop() - - if res == "error_exit": - sys.exit(-1) +if __name__ == "__main__": + sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + from klippy.printer import main -if __name__ == "__main__": main() diff --git a/klippy/mathutil.py b/klippy/mathutil.py index f892f25ac..b3d974538 100644 --- a/klippy/mathutil.py +++ b/klippy/mathutil.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging, multiprocessing, traceback -import queuelogger +from . import queuelogger def safe_float(v: str) -> float: diff --git a/klippy/mcu.py b/klippy/mcu.py index 9c0327520..5a80697cf 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -7,8 +7,8 @@ import math import os import zlib -import serialhdl, msgproto, pins, chelper, clocksync -from extras.danger_options import get_danger_options +from . import serialhdl, msgproto, pins, chelper, clocksync +from .extras.danger_options import get_danger_options class error(Exception): diff --git a/klippy/parsedump.py b/klippy/parsedump.py index 93d8d14e2..3b752cbf5 100755 --- a/klippy/parsedump.py +++ b/klippy/parsedump.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import os, sys, logging -import msgproto +from . import msgproto def read_dictionary(filename): diff --git a/klippy/printer.py b/klippy/printer.py new file mode 100644 index 000000000..b78a0e678 --- /dev/null +++ b/klippy/printer.py @@ -0,0 +1,601 @@ +# Main code for host side printer firmware +# +# Copyright (C) 2016-2020 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +import sys, os, gc, optparse, logging, time, collections, importlib, importlib.util + +from . import compat +from . import util, reactor, queuelogger, msgproto +from . import gcode, configfile, pins, mcu, toolhead, webhooks +from .extras.danger_options import get_danger_options +from . import APP_NAME + +message_ready = "Printer is ready" + +message_startup = """ +Printer is not ready +The klippy host software is attempting to connect. Please +retry in a few moments. +""" + +message_restart = """ +Once the underlying issue is corrected, use the "RESTART" +command to reload the config and restart the host software. +Printer is halted +""" + +message_protocol_error = """MCU Protocol error""" + +message_protocol_error1 = """ +This is frequently caused by running an older version of the +firmware on the MCU(s). Fix by recompiling and flashing the +firmware. +""" + +message_protocol_error2 = """ +Once the underlying issue is corrected, use the "RESTART" +command to reload the config and restart the host software. +""" + +message_mcu_connect_error = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Error configuring printer +""" + +message_shutdown = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Printer is shutdown +""" + + +class WaitInterruption(gcode.CommandError): + pass + + +class Printer: + config_error = configfile.error + command_error = gcode.CommandError + + def __init__(self, main_reactor, bglogger, start_args): + if sys.version_info[0] < 3: + logging.error("Kalico requires Python 3") + sys.exit(1) + + self.bglogger = bglogger + self.start_args = start_args + self.reactor = main_reactor + self.reactor.register_callback(self._connect) + self.state_message = message_startup + self.in_shutdown_state = False + self.run_result = None + self.event_handlers = {} + self.objects = collections.OrderedDict() + # 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" + elif self.state_message == message_startup: + category = "startup" + elif self.in_shutdown_state: + category = "shutdown" + 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") + + def add_object(self, name, obj): + if name in self.objects: + 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) + ] + 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] + extras_py_name = os.path.join( + os.path.dirname(__file__), "extras", module_name + ".py" + ) + extras_py_dirname = os.path.join( + os.path.dirname(__file__), "extras", module_name, "__init__.py" + ) + + plugins_py_dirname = os.path.join( + os.path.dirname(__file__), "plugins", module_name, "__init__.py" + ) + plugins_py_name = os.path.join( + os.path.dirname(__file__), "plugins", module_name + ".py" + ) + + found_in_extras = os.path.exists(extras_py_name) or os.path.exists( + extras_py_dirname + ) + found_in_plugins = os.path.exists(plugins_py_name) + found_in_plugins_dir = os.path.exists(plugins_py_dirname) + + if not any([found_in_extras, found_in_plugins, found_in_plugins_dir]): + if default is not configfile.sentinel: + return default + raise self.config_error("Unable to load module '%s'" % (section,)) + + if ( + found_in_extras + and (found_in_plugins or found_in_plugins_dir) + and not get_danger_options().allow_plugin_override + ): + raise self.config_error( + "Module '%s' found in both extras and plugins!" % (section,) + ) + + if found_in_plugins: + mod_spec = importlib.util.spec_from_file_location( + "klippy.extras." + module_name, plugins_py_name + ) + mod = importlib.util.module_from_spec(mod_spec) + mod_spec.loader.exec_module(mod) + elif found_in_plugins_dir: + mod_spec = importlib.util.spec_from_file_location( + "klippy.plugins." + module_name, plugins_py_dirname + ) + mod = importlib.util.module_from_spec(mod_spec) + mod_spec.loader.exec_module(mod) + else: + mod = importlib.import_module("klippy.extras." + module_name) + + init_func = "load_config" + if len(module_parts) > 1: + init_func = "load_config_prefix" + init_func = getattr(mod, init_func, None) + if init_func is None: + if default is not configfile.sentinel: + return default + 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) + config = pconfig.read_main_config() + self.load_object(config, "danger_options", None) + if ( + self.bglogger is not None + and get_danger_options().log_config_file_at_startup + ): + 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(""): + self.load_object(config, section_config.get_name(), None) + # Kalico on-by-default extras + for section_config in [ + "force_move", + "respond", + "exclude_object", + "telemetry", + ]: + self.load_object(config, section_config, None) + for m in [toolhead]: + m.add_printer_objects(config) + # Validate that there are no undefined parameters in the config file + error_on_unused = get_danger_options().error_on_unused_config_options + pconfig.check_unused_options(config, error_on_unused) + + def _build_protocol_error_message(self, e): + host_version = self.start_args["software_version"] + + msg_update = [] + msg_updated = [] + + for mcu_name, mcu_obj in self.lookup_objects("mcu"): + try: + mcu_version = mcu_obj.get_status()["mcu_version"] + except: + logging.exception("Unable to retrieve mcu_version from mcu_obj") + continue + + if mcu_version != host_version: + msg_update.append( + "%s: Current version %s" + % ( + mcu_name.split()[-1], + mcu_obj.get_status()["mcu_version"], + ) + ) + else: + msg_updated.append( + "%s: Current version %s" + % ( + mcu_name.split()[-1], + mcu_obj.get_status()["mcu_version"], + ) + ) + + if not len(msg_updated): + msg_updated.append("") + + version_msg = [ + "\nYour Kalico version is: %s\n" % host_version, + "MCU(s) which should be updated:", + "\n%s\n" % "\n".join(msg_update), + "Up-to-date MCU(s):", + "\n%s\n" % "\n".join(msg_updated), + ] + + msg = [ + message_protocol_error, + "", + " ".join(message_protocol_error1.splitlines())[1:], + "\n".join(version_msg), + " ".join(message_protocol_error2.splitlines())[1:], + "", + str(e), + ] + + return "\n".join(msg) + + def _connect(self, eventtime): + try: + self._read_config() + self.send_event("klippy:mcu_identify") + for cb in self.event_handlers.get("klippy:connect", []): + if self.state_message is not message_startup: + return + cb() + except (self.config_error, pins.error) as e: + logging.exception("Config error") + self._set_state("%s\n%s" % (str(e), message_restart)) + return + except msgproto.error as e: + logging.exception("Protocol error") + self._set_state(self._build_protocol_error_message(e)) + util.dump_mcu_build() + return + except mcu.error as e: + logging.exception("MCU error during connect") + self._set_state("%s%s" % (str(e), message_mcu_connect_error)) + util.dump_mcu_build() + 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, + ) + ) + return + try: + self._set_state(message_ready) + for cb in self.event_handlers.get("klippy:ready", []): + if self.state_message is not message_ready: + return + cb() + except Exception as e: + logging.exception("Unhandled exception during ready callback") + 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, + ) + # Enter main reactor loop + try: + self.reactor.run() + except: + msg = "Unhandled exception during run" + logging.exception(msg) + # Exception from a reactor callback - try to shutdown + try: + self.reactor.register_callback( + (lambda e: self.invoke_shutdown(msg)) + ) + self.reactor.run() + except: + logging.exception("Repeat unhandled exception during run") + # Another exception - try to exit + self.run_result = "error_exit" + # Check restart flags + run_result = self.run_result + try: + 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): + if self.in_shutdown_state: + return + logging.error("Transition to shutdown state: %s", msg) + self.in_shutdown_state = True + self._set_state("%s%s" % (msg, message_shutdown)) + for cb in self.event_handlers.get("klippy:shutdown", []): + try: + cb() + except: + logging.exception("Exception during shutdown handler") + logging.info( + "Reactor garbage collection: %s", self.reactor.get_gc_stats() + ) + + def invoke_async_shutdown(self, msg): + self.reactor.register_async_callback( + (lambda e: self.invoke_shutdown(msg)) + ) + + 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 + self.reactor.end() + + wait_interrupted = WaitInterruption + + def wait_while(self, condition_cb, error_on_cancel=True, interval=1.0): + """ + receives a callback + waits until callback returns False + (or is interrupted, or printer shuts down) + """ + gcode = self.lookup_object("gcode") + counter = gcode.get_interrupt_counter() + eventtime = self.reactor.monotonic() + while condition_cb(eventtime): + if self.is_shutdown() or counter != gcode.get_interrupt_counter(): + if error_on_cancel: + raise WaitInterruption("Command interrupted") + else: + return + eventtime = self.reactor.pause(eventtime + interval) + + +###################################################################### +# Startup +###################################################################### + + +def import_test(): + # Import all optional modules (used as a build test) + from .extras import danger_options + from unittest import mock + + danger_options.DANGER_OPTIONS = mock.Mock() + dname = os.path.dirname(__file__) + for mname in ["extras", "kinematics"]: + for fname in os.listdir(os.path.join(dname, mname)): + if fname.endswith(".py") and fname != "__init__.py": + module_name = fname[:-3] + else: + iname = os.path.join(dname, mname, fname, "__init__.py") + if not os.path.exists(iname): + continue + module_name = fname + importlib.import_module("klippy." + 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) + key = "dictionary_" + mcu_name + if parser.values.dictionary is None: + parser.values.dictionary = {} + parser.values.dictionary[key] = fname + + +def main(): + usage = "%prog [options] " + opts = optparse.OptionParser(usage, prog="klippy") + 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( + "--rotate-log-at-restart", + action="store_true", + help="rotate the log file at every restart", + ) + 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", + } + + 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() + else: + start_args["gcode_fd"] = util.create_pty(options.inputtty) + if options.debugoutput: + start_args["debugoutput"] = options.debugoutput + start_args.update(options.dictionary) + bglogger = None + if options.logfile: + start_args["log_file"] = options.logfile + bglogger = queuelogger.setup_bg_logging( + filename=options.logfile, + debuglevel=debuglevel, + rotate_log_at_restart=options.rotate_log_at_restart, + ) + if options.rotate_log_at_restart: + bglogger.doRollover() + else: + logging.getLogger().setLevel(debuglevel) + logging.info("=======================") + logging.info("Starting Klippy...") + git_info = util.get_git_version() + git_vers = git_info["version"] + + extra_git_desc = "" + 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["git_branch"] = git_info["branch"] + start_args["git_remote"] = git_info["remote"] + start_args["cpu_info"] = util.get_cpu_info() + if bglogger is not None: + versions = "\n".join( + [ + f"Args: {sys.argv}", + f"App Name: {APP_NAME}", + f"Git version: {repr(start_args['software_version'])}{extra_git_desc}", + f"CPU: {start_args['cpu_info']}", + f"Python: {repr(sys.version)}", + ] + ) + logging.info(versions) + elif not options.debugoutput: + logging.warning( + "No log file specified!" " Severe timing issues may result!" + ) + + compat.hotpatch_modules() + + gc.disable() + + # Start Printer() class + while True: + if bglogger is not None: + bglogger.clear_rollover_info() + 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"]: + break + time.sleep(1.0) + main_reactor.finalize() + main_reactor = printer = None + logging.info("Restarting printer") + start_args["start_reason"] = res + if options.rotate_log_at_restart and bglogger is not None: + bglogger.doRollover() + + if bglogger is not None: + bglogger.stop() + + if res == "error_exit": + sys.exit(-1) diff --git a/klippy/reactor.py b/klippy/reactor.py index 970504e57..10caae5fe 100644 --- a/klippy/reactor.py +++ b/klippy/reactor.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import os, gc, select, math, time, logging, queue import greenlet -import chelper, util +from . import chelper, util _NOW = 0.0 _NEVER = 9999999999999999.0 diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index c055ddd92..a257fedc7 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -6,8 +6,8 @@ import logging, threading, os import serial -import msgproto, chelper, util -from extras.danger_options import get_danger_options +from . import msgproto, chelper, util +from .extras.danger_options import get_danger_options class error(Exception): diff --git a/klippy/stepper.py b/klippy/stepper.py index 66ac60812..453d81a65 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import collections import math -import chelper +from . import chelper class error(Exception): diff --git a/klippy/toolhead.py b/klippy/toolhead.py index e15e9765d..32f636e22 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -4,9 +4,9 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging, importlib -import chelper -import kinematics.extruder -from extras.danger_options import get_danger_options +from . import chelper +from .kinematics import extruder +from .extras.danger_options import get_danger_options # Common suffixes: _d is distance (in mm), _v is velocity (in # mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in @@ -21,7 +21,6 @@ def __init__(self, toolhead, start_pos, end_pos, speed): self.end_pos = tuple(end_pos) self.accel = toolhead.max_accel self.junction_deviation = toolhead.junction_deviation - self.equilateral_corner_v2 = toolhead.equilateral_corner_v2 self.timing_callbacks = [] velocity = min(speed, toolhead.max_velocity) self.is_kinematic_move = True @@ -55,6 +54,7 @@ def __init__(self, toolhead, start_pos, end_pos, speed): self.delta_v2 = 2.0 * move_d * self.accel 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 @@ -65,6 +65,9 @@ def limit_speed(self, speed, 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]) @@ -79,6 +82,7 @@ def calc_junction(self, prev_move): extruder_v2, self.max_cruise_v2, prev_move.max_cruise_v2, + prev_move.next_junction_v2, prev_move.max_start_v2 + prev_move.delta_v2, ) # Find max velocity using "approximated centripetal velocity" @@ -279,11 +283,13 @@ def __init__(self, config): self.square_corner_velocity = config.getfloat( "square_corner_velocity", 5.0, minval=0.0 ) + self.orig_cfg = {} self.orig_cfg["max_velocity"] = self.max_velocity self.orig_cfg["max_accel"] = self.max_accel self.orig_cfg["min_cruise_ratio"] = self.min_cruise_ratio self.orig_cfg["square_corner_velocity"] = self.square_corner_velocity + self.equilateral_corner_v2 = 0.0 self.junction_deviation = self.max_accel_to_decel = 0 self._calc_junction_deviation() @@ -319,10 +325,10 @@ def __init__(self, config): # Create kinematics class gcode = self.printer.lookup_object("gcode") self.Coord = gcode.Coord - self.extruder = kinematics.extruder.DummyExtruder(self.printer) + self.extruder = extruder.DummyExtruder(self.printer) kin_name = config.get("kinematics") try: - mod = importlib.import_module("kinematics." + kin_name) + mod = importlib.import_module("klippy.kinematics." + kin_name) self.kin = mod.load_kinematics(self, config) except config.error as e: raise @@ -587,6 +593,11 @@ def set_position(self, newpos, homing_axes=()): 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: @@ -761,7 +772,7 @@ def get_max_velocity(self): def _calc_junction_deviation(self): scv2 = self.square_corner_velocity**2 - self.equilateral_corner_v2 = scv2 * (math.sqrt(2.0) - 1.0) + 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): @@ -867,4 +878,4 @@ def cmd_M204(self, gcmd): def add_printer_objects(config): config.get_printer().add_object("toolhead", ToolHead(config)) - kinematics.extruder.add_printer_objects(config) + extruder.add_printer_objects(config) diff --git a/klippy/webhooks.py b/klippy/webhooks.py index 676454501..d0079c534 100644 --- a/klippy/webhooks.py +++ b/klippy/webhooks.py @@ -4,9 +4,9 @@ # # This file may be distributed under the terms of the GNU GPLv3 license import logging, socket, os, sys, errno, json, collections -import gcode -from klippy import APP_NAME -from extras.danger_options import get_danger_options +from . import gcode +from . import APP_NAME +from .extras.danger_options import get_danger_options REQUEST_LOG_SIZE = 20 diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py index 4c2b23fed..2c18281fa 100644 --- a/scripts/buildcommands.py +++ b/scripts/buildcommands.py @@ -4,11 +4,12 @@ # Copyright (C) 2016-2024 Kevin O'Connor # # 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 +import sys, os, subprocess, optparse, logging, pathlib, shlex, socket, time +import traceback, json, zlib -sys.path.append("./klippy") -import msgproto # noqa: E402 +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy import msgproto FILEHEADER = """ /* DO NOT EDIT! This is an autogenerated file. See scripts/buildcommands.py. */ diff --git a/scripts/calibrate_shaper.py b/scripts/calibrate_shaper.py index 91833c590..93e48d43e 100755 --- a/scripts/calibrate_shaper.py +++ b/scripts/calibrate_shaper.py @@ -6,14 +6,13 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. from __future__ import print_function -import importlib, optparse, os, sys +import optparse, sys, pathlib 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") +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy.extras import shaper_calibrate MAX_TITLE_LENGTH = 65 diff --git a/scripts/ci-build.sh b/scripts/ci-build.sh index 555786814..7543d9860 100755 --- a/scripts/ci-build.sh +++ b/scripts/ci-build.sh @@ -63,6 +63,7 @@ fi ###################################################################### start_test klippy "Test klippy import (Python3)" +# I'm leaving this with klippy/klippy.py so we test that compatibility $PYTHON klippy/klippy.py --import-test finish_test klippy "Test klippy import (Python3)" diff --git a/klippy/console.py b/scripts/console.py similarity index 98% rename from klippy/console.py rename to scripts/console.py index b7b6e34f2..defd5f6de 100755 --- a/klippy/console.py +++ b/scripts/console.py @@ -4,8 +4,10 @@ # Copyright (C) 2016-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, optparse, os, re, logging -import util, reactor, serialhdl, msgproto, clocksync +import sys, optparse, os, re, logging, pathlib + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) +from klippy import util, reactor, serialhdl, msgproto, clocksync help_txt = """ This is a debugging console for the Klipper micro-controller. diff --git a/scripts/dump_mcu.py b/scripts/dump_mcu.py index 24e7bea0d..35ec6aba0 100755 --- a/scripts/dump_mcu.py +++ b/scripts/dump_mcu.py @@ -5,17 +5,17 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. -import clocksync -import serialhdl -import reactor import sys import argparse import os import traceback import logging +import pathlib + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy import clocksync, serialhdl, reactor -KLIPPER_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) -sys.path.append(os.path.join(KLIPPER_DIR, "klippy")) ########################################################### # diff --git a/scripts/graph_accelerometer.py b/scripts/graph_accelerometer.py index b50e0f728..cf620d75e 100755 --- a/scripts/graph_accelerometer.py +++ b/scripts/graph_accelerometer.py @@ -5,14 +5,14 @@ # Copyright (C) 2020 Dmitry Butyugin # # This file may be distributed under the terms of the GNU GPLv3 license. -import importlib, optparse, os, sys +import optparse, os, pathlib, 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") +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy.extras import shaper_calibrate # noqa:E402 + MAX_TITLE_LENGTH = 65 diff --git a/scripts/make_version.py b/scripts/make_version.py index 6617be6be..dcdcafa3c 100644 --- a/scripts/make_version.py +++ b/scripts/make_version.py @@ -6,13 +6,15 @@ # This file may be distributed under the terms of the GNU GPLv3 license. from __future__ import print_function -import util import argparse import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "../klippy")) +KLIPPER_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) +sys.path.append(KLIPPER_DIR) + +from klippy import util # noqa: E402 def main(argv): diff --git a/scripts/parsecandump.py b/scripts/parsecandump.py index e5d6c577c..953aa3893 100755 --- a/scripts/parsecandump.py +++ b/scripts/parsecandump.py @@ -4,15 +4,11 @@ # Copyright (C) 2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, optparse +import sys, pathlib, optparse +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) -def import_msgproto(): - global msgproto - # Load msgproto.py module - kdir = os.path.join(os.path.dirname(__file__), "..", "klippy") - sys.path.append(kdir) - import msgproto +from klippy import msgproto def read_dictionary(filename): @@ -143,7 +139,6 @@ def main(): canfilename, canid, dictfilename = args canid = int(canid, 16) - import_msgproto() dictionary = read_dictionary(dictfilename) canfile = open(canfilename, "r") diff --git a/scripts/spi_flash/spi_flash.py b/scripts/spi_flash/spi_flash.py index 530c28122..5266e4363 100644 --- a/scripts/spi_flash/spi_flash.py +++ b/scripts/spi_flash/spi_flash.py @@ -17,10 +17,7 @@ import json import board_defs import fatfs_lib -import reactor -import serialhdl -import clocksync -import mcu +from klippy import reactor, serialhdl, clocksync, mcu ########################################################### # diff --git a/scripts/test_klippy.py b/scripts/test_klippy.py index 0b8a27e02..35b16ddee 100644 --- a/scripts/test_klippy.py +++ b/scripts/test_klippy.py @@ -113,7 +113,8 @@ def launch_test( ) args = [ sys.executable, - "./klippy/klippy.py", + "-m", + "klippy", config_fname, "-i", gcode_fname, diff --git a/test/klippy/commands.test b/test/klippy/commands.test index 511643652..fcf095951 100644 --- a/test/klippy/commands.test +++ b/test/klippy/commands.test @@ -44,5 +44,9 @@ RESET_VELOCITY_LIMIT SET_PRESSURE_ADVANCE EXTRUDER=extruder ADVANCE=.001 SET_PRESSURE_ADVANCE ADVANCE=.002 ADVANCE_LOOKAHEAD_TIME=.001 +# test telemetry +TELEMETRY_ENABLE +TELEMETRY_EXAMPLE + # Restart command (must be last in test) RESTART