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:
mangavalkandClaude Opus 5.5 committed 2026-10-18 23:06:49 +02:00
1 parent 61fa8acb05
commit 8eef0ee298
3 files changed
+47 -6

No files matched your search

+39 -1
View File
@@ -46,6 +46,38 @@ EXT_MAGIC = 0xBD
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:
def __init__(self, config):
self.printer = config.get_printer()
@@ -76,7 +108,13 @@ class BDPGuard:
def _handle_connect(self):
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):
self._stop_timer()
+7 -4
View File
@@ -35,7 +35,6 @@ class BDPressureCheck:
def __init__(self, config):
self.printer = config.get_printer()
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
self.i2c = None
self.sensor = None
self.status = {'available': False, 'running': False, 'peak': 0,
'above_ms': 0, 'longest_ms': 0, 'delta': 0}
@@ -50,9 +49,13 @@ class BDPressureCheck:
desc="Read the bd_pressure load check values")
def _handle_connect(self):
sensor = self.printer.lookup_object(self.sensor_name)
self.sensor = sensor
self.i2c = getattr(sensor, 'i2c', None)
self.sensor = self.printer.lookup_object(self.sensor_name)
@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):
if self.i2c is None: