diff --git a/README.md b/README.md index 697e895..cf939ee 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ everything below, grouped by topic. Comment out what you do not use there. | File | For | |---|---| | `xy_drift.py` | `AFC_XY_SAFETY_CHECK`: measures lost XY steps from the stepper step counts at the endstop triggers (CoreXY). The toolhead position after homing cannot show lost steps. | -| `bdwidth_status.py` | exposes bdwidth's motion counter and width to macros | +| `bdwidth_status.py` | exposes bdwidth's motion counter and width to macros, and keeps a USB dropout of bdwidth (port: usb) from shutting down Klipper: the read is retried every 10 s and the port reopened | | `bdpressure_check.py` | bd_pressure load check (`BDP_CHECK_START/STOP/QUERY`). Needs the load-check firmware from the bd_pressure `load-check-extension` branch; on stock firmware it reports "not available" and does nothing. | ### Scripts (`scripts/`) diff --git a/extras/__pycache__/bdwidth_status.cpython-312.pyc b/extras/__pycache__/bdwidth_status.cpython-312.pyc new file mode 100644 index 0000000..3984427 Binary files /dev/null and b/extras/__pycache__/bdwidth_status.cpython-312.pyc differ diff --git a/extras/bdwidth_status.py b/extras/bdwidth_status.py index d52263c..fb596b7 100644 --- a/extras/bdwidth_status.py +++ b/extras/bdwidth_status.py @@ -26,6 +26,19 @@ # only starts reading on the first SET_BDWIDTH ... ENABLE # linear_motion - counts per mm (motion_linear_coefficient) # sample_time - seconds between sensor reads +# usb_ok - False while the USB connection is down (port: usb) +# usb_errors - USB read errors since Klipper start +# +# USB guard (port: usb): bdwidth reads the sensor from a reactor timer without +# any error handling, so one USB dropout of its CH340 (write failed: [Errno 5] +# Input/output error) shut down Klipper mid print. This module wraps the +# sensor's Read_bdwidth at connect: on a USB error it closes the port, reports +# it and returns False, after which bdwidth tries again 10 s later; the next +# reads reopen the port (the /dev/serial/by-id path returns when the device +# reconnects). bdwidth.py itself is not changed. + + +import logging class BDWidthStatus: @@ -34,11 +47,52 @@ class BDWidthStatus: self.name = config.get('sensor', 'fila_width_0') self.sensor_name = 'bdwidth ' + self.name self.sensor = None + self.usb_ok = True + self.usb_errors = 0 + self._read = None self.printer.register_event_handler('klippy:connect', self._handle_connect) def _handle_connect(self): self.sensor = self.printer.lookup_object(self.sensor_name) + if (getattr(self.sensor, 'port', None) == 'usb' + and callable(getattr(self.sensor, 'Read_bdwidth', None)) + and getattr(self.sensor, 'usb', None) is not None): + self._read = self.sensor.Read_bdwidth + self.sensor.Read_bdwidth = self._guarded_read + logging.info("bdwidth_status: USB guard active for %s", self.name) + + def _respond(self, msg): + logging.warning(msg) + try: + self.printer.lookup_object('gcode').respond_info(msg) + except Exception: + pass + + def _guarded_read(self): + # Called from bdwidth's reactor timer: must never raise + usb = self.sensor.usb + try: + if not usb.is_open: + usb.open() + result = self._read() + except Exception as e: + self.usb_errors += 1 + try: + usb.close() + except Exception: + pass + if self.usb_ok: + self.usb_ok = False + self._respond("bdwidth %s: USB error (%s), print continues " + "without width readings, retrying" + % (self.name, e)) + return False + if not self.usb_ok: + self.usb_ok = True + self._respond("bdwidth %s: USB connection restored after %d " + "errors" % (self.name, self.usb_errors)) + return result def get_status(self, eventtime): s = self.sensor @@ -64,6 +118,8 @@ class BDWidthStatus: 'active': str(getattr(s, 'is_active', 'disable')), 'linear_motion': getattr(s, 'linear_motion', 42.8), 'sample_time': getattr(s, 'sample_time', 1.), + 'usb_ok': self.usb_ok, + 'usb_errors': self.usb_errors, }