bdp_guard: one lock for all I2C transfers of the bd_pressure
The guard's timer read the sensor while G28 ran; bdpressure.py writes the
probe mode at every homing move, and Klipper allows only one I2C request
at a time per device: KeyError ('i2c_response', 1), Klipper shut down
during HOME. The guard now wraps the sensor's i2c in a reactor mutex at
connect; bdpressure and bdpressure_check (now looks up sensor.i2c at
every use) go through it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
61fa8acb05
commit
8eef0ee298
3 files changed
+47
-6
No files matched your search
@@ -54,7 +54,7 @@ everything below, grouped by topic. Comment out what you do not use there.
|
|||||||
| `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. |
|
| `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 (`present: False` with neutral values when no bdwidth is configured, so the macros also work without it), and keeps a USB dropout of bdwidth (port: usb) from shutting down Klipper: the read is retried every 10 s and the port reopened |
|
| `bdwidth_status.py` | exposes bdwidth's motion counter and width to macros (`present: False` with neutral values when no bdwidth is configured, so the macros also work without it), 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. Also reports how many readings the last `PA_CALIBRATE` got (`pa_points`, `pa_distinct`), used to reject runs with a silent or stuck sensor. |
|
| `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. Also reports how many readings the last `PA_CALIBRATE` got (`pa_points`, `pa_distinct`), used to reject runs with a silent or stuck sensor. |
|
||||||
| `bdp_guard.py` | nozzle collision guard: while armed (`BDP_GUARD_ARM/DISARM/QUERY`) reads the bd_pressure force every 20 ms over I2C and, in stop mode, shuts Klipper down when it stays above the threshold. Reports reads, peak and errors per phase in the console and klippy.log. Needs the load-check firmware. |
|
| `bdp_guard.py` | nozzle collision guard: while armed (`BDP_GUARD_ARM/DISARM/QUERY`) reads the bd_pressure force every 20 ms over I2C and, in stop mode, shuts Klipper down when it stays above the threshold. Reports reads, peak and errors per phase in the console and klippy.log. Puts a lock on the sensor's I2C connection that `bdpressure.py` and `bdpressure_check.py` also go through (Klipper allows one I2C request at a time per device). Needs the load-check firmware. |
|
||||||
|
|
||||||
### Scripts (`scripts/`)
|
### Scripts (`scripts/`)
|
||||||
|
|
||||||
|
|||||||
+39
-1
@@ -46,6 +46,38 @@ EXT_MAGIC = 0xBD
|
|||||||
MODES = ('log', 'stop')
|
MODES = ('log', 'stop')
|
||||||
|
|
||||||
|
|
||||||
|
# Klipper allows only one I2C request at a time per device: a second one
|
||||||
|
# while the first waits for its answer raises KeyError ('i2c_response', oid)
|
||||||
|
# and shuts Klipper down. The guard reads from a timer while G-code runs,
|
||||||
|
# and bdpressure.py writes the probe mode at every homing move (seen: G28
|
||||||
|
# during an armed guard), so all transfers of the sensor take this lock.
|
||||||
|
# The guard puts it on the sensor object at connect; bdpressure and
|
||||||
|
# bdpressure_check use sensor.i2c and so get it too.
|
||||||
|
class LockedI2C:
|
||||||
|
def __init__(self, i2c, mutex):
|
||||||
|
self._i2c = i2c
|
||||||
|
self._mutex = mutex
|
||||||
|
|
||||||
|
def i2c_read(self, *args, **kwargs):
|
||||||
|
with self._mutex:
|
||||||
|
return self._i2c.i2c_read(*args, **kwargs)
|
||||||
|
|
||||||
|
def i2c_write(self, *args, **kwargs):
|
||||||
|
with self._mutex:
|
||||||
|
return self._i2c.i2c_write(*args, **kwargs)
|
||||||
|
|
||||||
|
def i2c_write_wait_ack(self, *args, **kwargs):
|
||||||
|
with self._mutex:
|
||||||
|
return self._i2c.i2c_write_wait_ack(*args, **kwargs)
|
||||||
|
|
||||||
|
def i2c_transfer(self, *args, **kwargs):
|
||||||
|
with self._mutex:
|
||||||
|
return self._i2c.i2c_transfer(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._i2c, name)
|
||||||
|
|
||||||
|
|
||||||
class BDPGuard:
|
class BDPGuard:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
@@ -76,7 +108,13 @@ class BDPGuard:
|
|||||||
|
|
||||||
def _handle_connect(self):
|
def _handle_connect(self):
|
||||||
self.sensor = self.printer.lookup_object(self.sensor_name, None)
|
self.sensor = self.printer.lookup_object(self.sensor_name, None)
|
||||||
self.i2c = getattr(self.sensor, 'i2c', None)
|
i2c = getattr(self.sensor, 'i2c', None)
|
||||||
|
if i2c is not None and not isinstance(i2c, LockedI2C):
|
||||||
|
# Every user of the sensor (bdpressure, bdpressure_check, this
|
||||||
|
# guard) now goes through one lock, see LockedI2C
|
||||||
|
i2c = LockedI2C(i2c, self.reactor.mutex())
|
||||||
|
self.sensor.i2c = i2c
|
||||||
|
self.i2c = i2c
|
||||||
|
|
||||||
def _handle_shutdown(self):
|
def _handle_shutdown(self):
|
||||||
self._stop_timer()
|
self._stop_timer()
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class BDPressureCheck:
|
|||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
|
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
|
||||||
self.i2c = None
|
|
||||||
self.sensor = None
|
self.sensor = None
|
||||||
self.status = {'available': False, 'running': False, 'peak': 0,
|
self.status = {'available': False, 'running': False, 'peak': 0,
|
||||||
'above_ms': 0, 'longest_ms': 0, 'delta': 0}
|
'above_ms': 0, 'longest_ms': 0, 'delta': 0}
|
||||||
@@ -50,9 +49,13 @@ class BDPressureCheck:
|
|||||||
desc="Read the bd_pressure load check values")
|
desc="Read the bd_pressure load check values")
|
||||||
|
|
||||||
def _handle_connect(self):
|
def _handle_connect(self):
|
||||||
sensor = self.printer.lookup_object(self.sensor_name)
|
self.sensor = self.printer.lookup_object(self.sensor_name)
|
||||||
self.sensor = sensor
|
|
||||||
self.i2c = getattr(sensor, 'i2c', None)
|
@property
|
||||||
|
def i2c(self):
|
||||||
|
# Looked up at every use: bdp_guard replaces the sensor's i2c with a
|
||||||
|
# locked wrapper at connect, possibly after this module's connect.
|
||||||
|
return getattr(self.sensor, 'i2c', None)
|
||||||
|
|
||||||
def _read(self):
|
def _read(self):
|
||||||
if self.i2c is None:
|
if self.i2c is None:
|
||||||
|
|||||||
Reference in new issue
Block a user