From ff1b687acbe3d82a2eeb91290e44f2d84ead80a6 Mon Sep 17 00:00:00 2001 From: mikebcbc Date: Fri, 16 Feb 2024 15:50:41 -0500 Subject: [PATCH 1/3] inital files to get baseline working --- .gitignore | 1 + klippy/chelper/Makefile | 23 ++++++++ klippy/chelper/__init__.py | 14 ++--- klippy/extras/gcode_shell_command.py | 87 ++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 klippy/chelper/Makefile create mode 100644 klippy/extras/gcode_shell_command.py diff --git a/.gitignore b/.gitignore index f9521672a473..2f1d3875cf3b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ out .config .config.old klippy/.version +klippy/extras/cartographer.py diff --git a/klippy/chelper/Makefile b/klippy/chelper/Makefile new file mode 100644 index 000000000000..d2575d507538 --- /dev/null +++ b/klippy/chelper/Makefile @@ -0,0 +1,23 @@ +CC = gcc +CFLAGS := -Wall -g -O2 -fPIC -flto -fwhole-program -fno-use-linker-plugin + +OBJECTS = pyhelper.o serialqueue.o stepcompress.o itersolve.o trapq.o \ + pollreactor.o msgblock.o trdispatch.o \ + kin_cartesian.o kin_corexy.o kin_corexz.o kin_delta.o \ + kin_deltesian.o kin_polar.o kin_rotary_delta.o kin_winch.o \ + kin_extruder.o kin_shaper.o + +DIST_LIB = c_helper.so + +all: $(DIST_LIB) + +clean: + @echo "Cleaning up directory." + rm -f *.a *.o $(DIST_LIB) *~ + +$(DIST_LIB):$(OBJECTS) + $(CC) -shared -fPIC -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -g -c -o $@ $< + diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index e4199561d018..1c051d9da1c8 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -277,13 +277,13 @@ 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_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))) + #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))) FFI_main = cffi.FFI() for d in defs_all: FFI_main.cdef(d) diff --git a/klippy/extras/gcode_shell_command.py b/klippy/extras/gcode_shell_command.py new file mode 100644 index 000000000000..bb38ae551f17 --- /dev/null +++ b/klippy/extras/gcode_shell_command.py @@ -0,0 +1,87 @@ +# Run a shell command via gcode +# +# Copyright (C) 2019 Eric Callahan +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import os +import shlex +import subprocess +import logging + +class ShellCommand: + def __init__(self, config): + self.name = config.get_name().split()[-1] + self.printer = config.get_printer() + self.gcode = self.printer.lookup_object('gcode') + cmd = config.get('command') + cmd = os.path.expanduser(cmd) + self.command = shlex.split(cmd) + self.timeout = config.getfloat('timeout', 2., above=0.) + self.verbose = config.getboolean('verbose', True) + self.proc_fd = None + self.partial_output = "" + self.gcode.register_mux_command( + "RUN_SHELL_COMMAND", "CMD", self.name, + self.cmd_RUN_SHELL_COMMAND, + desc=self.cmd_RUN_SHELL_COMMAND_help) + + def _process_output(self, eventime): + if self.proc_fd is None: + return + try: + data = os.read(self.proc_fd, 4096) + except Exception: + pass + data = self.partial_output + data.decode() + if '\n' not in data: + self.partial_output = data + return + elif data[-1] != '\n': + split = data.rfind('\n') + 1 + self.partial_output = data[split:] + data = data[:split] + else: + self.partial_output = "" + self.gcode.respond_info(data) + + cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command" + def cmd_RUN_SHELL_COMMAND(self, params): + gcode_params = params.get('PARAMS','') + gcode_params = shlex.split(gcode_params) + reactor = self.printer.get_reactor() + try: + proc = subprocess.Popen( + self.command + gcode_params, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except Exception: + logging.exception( + "shell_command: Command {%s} failed" % (self.name)) + raise self.gcode.error("Error running command {%s}" % (self.name)) + if self.verbose: + self.proc_fd = proc.stdout.fileno() + self.gcode.respond_info("Running Command {%s}...:" % (self.name)) + hdl = reactor.register_fd(self.proc_fd, self._process_output) + eventtime = reactor.monotonic() + endtime = eventtime + self.timeout + complete = False + while eventtime < endtime: + eventtime = reactor.pause(eventtime + .05) + if proc.poll() is not None: + complete = True + break + if not complete: + proc.terminate() + if self.verbose: + if self.partial_output: + self.gcode.respond_info(self.partial_output) + self.partial_output = "" + if complete: + msg = "Command {%s} finished\n" % (self.name) + else: + msg = "Command {%s} timed out" % (self.name) + self.gcode.respond_info(msg) + reactor.unregister_fd(hdl) + self.proc_fd = None + + +def load_config_prefix(config): + return ShellCommand(config) From 9504a9a15cb43a23657ce990308e83cfdfbf198a Mon Sep 17 00:00:00 2001 From: B Date: Wed, 31 Jan 2024 10:34:51 -0500 Subject: [PATCH 2/3] noncritical mcus (#42) --- klippy/clocksync.py | 4 +++ klippy/mcu.py | 84 ++++++++++++++++++++++++++++++++++++++++++--- klippy/serialhdl.py | 38 ++++++++++++++++++-- 3 files changed, 119 insertions(+), 7 deletions(-) diff --git a/klippy/clocksync.py b/klippy/clocksync.py index 80ed9db61f25..995c1b791330 100644 --- a/klippy/clocksync.py +++ b/klippy/clocksync.py @@ -27,6 +27,10 @@ def __init__(self, reactor): self.clock_avg = self.clock_covariance = 0. self.prediction_variance = 0. self.last_prediction_time = 0. + + def disconnect(self): + self.reactor.update_timer(self.get_clock_timer, self.reactor.NEVER) + def connect(self, serial): self.serial = serial self.mcu_freq = serial.msgparser.get_constant_float('CLOCK_FREQ') diff --git a/klippy/mcu.py b/klippy/mcu.py index f9b547c94479..5b1ae0730d72 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -523,6 +523,7 @@ def _handle_analog_in_state(self, params): class MCU: error = error def __init__(self, config, clocksync): + self._config = config self._printer = printer = config.get_printer() self._clocksync = clocksync self._reactor = printer.get_reactor() @@ -580,6 +581,18 @@ def __init__(self, config, clocksync): self._mcu_tick_avg = 0. self._mcu_tick_stddev = 0. self._mcu_tick_awake = 0. + + # noncritical mcus + self.non_critical_recon_timer = self._reactor.register_timer( + self.non_critical_recon_event + ) + self.is_non_critical = config.getboolean("is_non_critical", False) + self._non_critical_disconnected = False + # self.last_noncrit_recon_eventtime = None + self.reconnect_interval = ( + config.getfloat("reconnect_interval", 2.0) + 0.12 + ) # add small change to not collide with other events + # Register handlers printer.register_event_handler("klippy:firmware_restart", self._firmware_restart) @@ -614,9 +627,11 @@ def _handle_shutdown(self, params): prefix = "Previous MCU '%s' shutdown: " % (self._name,) self._printer.invoke_async_shutdown(prefix + msg + error_help(msg)) def _handle_starting(self, params): - if not self._is_shutdown: - self._printer.invoke_async_shutdown("MCU '%s' spontaneous restart" - % (self._name,)) + if not self._is_shutdown and not self.is_non_critical: + 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") @@ -647,6 +662,20 @@ def _connect_file(self, pace=False): def dummy_estimated_print_time(eventtime): return 0. self.estimated_print_time = dummy_estimated_print_time + + def handle_non_critical_disconnect(self): + self._non_critical_disconnected = True + self._clocksync.disconnect() + self._disconnect() + self._reactor.update_timer( + self.non_critical_recon_timer, self._reactor.NOW + ) + logging.info("mcu: %s disconnected", self._name) + + def non_critical_recon_event(self, eventtime): + self.recon_mcu() + return eventtime + self.reconnect_interval + def _send_config(self, prev_crc): # Build config commands for cb in self._config_callbacks: @@ -660,6 +689,7 @@ def _send_config(self, prev_crc): 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) + logging.info("command: %s", cmdlist[i]) # Calculate config CRC encoded_config = '\n'.join(self._config_cmds).encode() config_crc = zlib.crc32(encoded_config) & 0xffffffff @@ -713,7 +743,36 @@ def _log_info(self): "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 recon_mcu(self): + res = self._mcu_identify() + if not res: + return + self.reset_to_initial_state() + self._connect() + self._reactor.update_timer( + self.non_critical_recon_timer, self._reactor.NEVER + ) + self._reactor.unregister_timer(self.non_critical_recon_timer) + self.last_noncrit_recon_eventtime = None + logging.info("mcu: %s reconnected", self._name) + + def reset_to_initial_state(self): + self._oid_count = 0 + self._config_cmds = [] + self._restart_cmds = [] + self._init_cmds = [] + self._reserved_move_slots = 0 + self._stepqueues = [] + self._steppersync = None + def _connect(self): + if self._non_critical_disconnected: + self.non_critical_recon_timer = self._reactor.register_timer( + self.non_critical_recon_event, + self._reactor.NOW + self.reconnect_interval, + ) + return config_params = self._send_get_config() if not config_params['is_config']: if self._restart_method == 'rpi_usb': @@ -747,7 +806,17 @@ def _connect(self): logging.info(move_msg) log_info = self._log_info() + "\n" + move_msg self._printer.set_rollover_info(self._name, log_info, log=False) + + def _check_serial_exists(self): + rts = self._restart_method != "cheetah" + return self._serial.check_connect(self._serialport, self._baud, rts) + def _mcu_identify(self): + if self.is_non_critical and not self._check_serial_exists(): + self._non_critical_disconnected = True + return False + else: + self._non_critical_disconnected = False if self.is_fileoutput(): self._connect_file() else: @@ -799,6 +868,8 @@ def _mcu_identify(self): self.register_response(self._handle_shutdown, 'shutdown') self.register_response(self._handle_shutdown, 'is_shutdown') self.register_response(self._handle_mcu_stats, 'stats') + return True + # Config creation helpers def setup_pin(self, pin_type, pin_params): pcs = {'endstop': MCU_endstop, @@ -903,7 +974,9 @@ def _restart_rpi_usb(self): self._reactor.pause(self._reactor.monotonic() + 2.) chelper.run_hub_ctrl(1) def _firmware_restart(self, force=False): - if self._is_mcu_bridge and not force: + if ( + self._is_mcu_bridge and not force + ) or self._non_critical_disconnected: return if self._restart_method == 'rpi_usb': self._restart_rpi_usb() @@ -945,6 +1018,9 @@ def check_active(self, print_time, eventtime): if (self._clocksync.is_active() or self.is_fileoutput() or self._is_timeout): return + if self.is_non_critical: + self.handle_non_critical_disconnect() + return self._is_timeout = True logging.info("Timeout with MCU '%s' (eventtime=%f)", self._name, eventtime) diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index f9e542d35029..10b57006eef3 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -178,7 +178,11 @@ def connect_uart(self, serialport, baud, rts=True): logging.info("%sStarting serial connect", self.warn_prefix) start_time = self.reactor.monotonic() while 1: - if self.reactor.monotonic() > start_time + 90.: + if ( + self.serialqueue is not None + ): # if we're already connected, don't recon + break + if self.reactor.monotonic() > start_time + 90.0: self._error("Unable to connect") try: serial_dev = serial.Serial(baudrate=baud, timeout=0, @@ -195,6 +199,18 @@ def connect_uart(self, serialport, baud, rts=True): ret = self._start_session(serial_dev) if ret: break + + def check_connect(self, serialport, baud, rts=True): + serial_dev = serial.Serial(baudrate=baud, timeout=0, exclusive=False) + serial_dev.port = serialport + serial_dev.rts = rts + try: + serial_dev.open() + except Exception: + return False + serial_dev.close() + return True + def connect_file(self, debugoutput, dictionary, pace=False): self.serial_dev = debugoutput self.msgparser.process_identify(dictionary, decompress=False) @@ -239,9 +255,25 @@ def register_response(self, callback, name, oid=None): 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) + if self.serialqueue is None: + logging.info( + "%sSerial connection closed, cmd: %s", + self.warn_prefix, + repr(cmd), + ) + return + 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): + if self.serialqueue is None: + logging.info( + "%sSerial connection closed, in wait ack, cmd: %s", + self.warn_prefix, + repr(cmd), + ) + return self.last_notify_id += 1 nid = self.last_notify_id completion = self.reactor.completion() From cea3ed47207f7af6ba1977530b864ed5d0d9cd03 Mon Sep 17 00:00:00 2001 From: mikebcbc Date: Fri, 16 Feb 2024 16:07:44 -0500 Subject: [PATCH 3/3] temp sens fix --- klippy/extras/temperature_mcu.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index 585ec4c1d20d..21ad24379ce1 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -76,6 +76,7 @@ def _mcu_identify(self): ('stm32l4', self.config_stm32g0), ('stm32h723', self.config_stm32h723), ('stm32h7', self.config_stm32h7), + ('gd32f303xe', self.config_gd32f303xe), ('', self.config_unknown)] for name, func in cfg_funcs: if self.mcu_type.startswith(name): @@ -157,6 +158,9 @@ def config_stm32h7(self): 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) + def config_gd32f303xe(self): + self.slope = 3.3 / -.004100 + self.base_temperature = self.calc_base(25., 1.45 / 3.3) def read16(self, addr): params = self.debug_read_cmd.send([1, addr]) return params['val']