This commit is contained in:
KevinOConnor 2026-03-01 21:32:28 -03:00 committed by GitHub
commit 5e803fba28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 112 additions and 58 deletions

View file

@ -85,8 +85,6 @@ class ADS1220:
if drdy_pin_mcu != self.mcu:
raise config.error("ADS1220 config error: SPI communication and"
" data_ready_pin must be on the same MCU")
# Bulk Sensor Setup
self.bulk_queue = bulk_sensor.BulkDataQueue(self.mcu, oid=self.oid)
# Clock tracking
chip_smooth = self.sps * UPDATE_INTERVAL * 2
# Measurement conversion

View file

@ -112,14 +112,16 @@ class BatchWebhooksClient:
self.cconn.send(tmp)
return True
SENSOR_BULK_FMT = "sensor_bulk_data oid=%c sequence=%hu data=%*s"
# Helper class to store incoming messages in a queue
class BulkDataQueue:
def __init__(self, mcu, msg_name="sensor_bulk_data", oid=None):
def __init__(self, mcu, msg_fmt=SENSOR_BULK_FMT, oid=None):
# Measurement storage (accessed from background thread)
self.lock = threading.Lock()
self.raw_samples = []
# Register callback with mcu
mcu.register_response(self._handle_data, msg_name, oid)
mcu.register_serial_response(self._handle_data, msg_fmt, oid)
def _handle_data(self, params):
with self.lock:
self.raw_samples.append(params)

View file

@ -248,8 +248,10 @@ class MCU_I2C:
"i2c_transfer oid=%c write=%*s read_len=%u",
cq=self.cmd_queue)
if self.async_write_only:
self.mcu.register_response(self._async_write_status,
"i2c_response", self.oid)
self.mcu.register_serial_response(
self._async_write_status,
"i2c_response oid=%c i2c_bus_status=%c response=%*s",
self.oid)
self._configured = True
def i2c_write_noack(self, data, minclock=0, reqclock=0):
if self.async_write_only:

View file

@ -52,8 +52,9 @@ class MCU_buttons:
" rest_ticks=%d retransmit_count=%d invert=%d" % (
self.oid, clock, rest_ticks, RETRANSMIT_COUNT,
self.invert), is_init=True)
self.mcu.register_response(self.handle_buttons_state,
"buttons_state", self.oid)
self.mcu.register_serial_response(
self.handle_buttons_state,
"buttons_state oid=%c ack_count=%c state=%*s", self.oid)
def handle_buttons_state(self, params):
# Expand the message ack_count from 8-bit
ack_count = self.ack_count

View file

@ -40,8 +40,9 @@ class PrinterCANBusStats:
"canbus_status rx_error=%u tx_error=%u tx_retries=%u"
" canbus_bus_state=%u")
# Register usb_canbus_state message handling (for usb to canbus bridge)
self.mcu.register_response(self.handle_usb_canbus_state,
"usb_canbus_state")
if self.mcu.check_valid_response("usb_canbus_state discard=%u"):
self.mcu.register_serial_response(self.handle_usb_canbus_state,
"usb_canbus_state discard=%u")
# Register periodic query timer
self.reactor.register_timer(self.query_event, self.reactor.NOW)
def handle_usb_canbus_state(self, params):

View file

@ -26,8 +26,9 @@ class DS18B20:
)
self._mcu = mcu.get_printer_mcu(self.printer, config.get('sensor_mcu'))
self.oid = self._mcu.create_oid()
self._mcu.register_response(self._handle_ds18b20_response,
"ds18b20_result", self.oid)
self._mcu.register_serial_response(
self._handle_ds18b20_response,
"ds18b20_result oid=%c next_clock=%u value=%i fault=%u", self.oid)
self._mcu.register_config_callback(self._build_config)
def _build_config(self):

View file

@ -42,8 +42,6 @@ class HX71xBase:
# gain/channel choices
self.gain_channel = int(config.getchoice('gain', gain_options,
default=default_gain))
## Bulk Sensor Setup
self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=self.oid)
# Clock tracking
chip_smooth = self.sps * UPDATE_INTERVAL * 2
self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "<i")

View file

@ -28,8 +28,10 @@ class MCU_counter:
self._mcu.add_config_cmd(
"query_counter oid=%d clock=%d poll_ticks=%d sample_ticks=%d"
% (self._oid, clock, self._poll_ticks, sample_ticks), is_init=True)
self._mcu.register_response(self._handle_counter_state,
"counter_state", self._oid)
self._mcu.register_serial_response(
self._handle_counter_state,
"counter_state oid=%c next_clock=%u count=%u count_clock=%u",
self._oid)
# Callback is called periodically every sample_time
def setup_callback(self, cb):

View file

@ -29,8 +29,9 @@ class SensorBase:
self.mcu = mcu = self.spi.get_mcu()
# Reader chip configuration
self.oid = oid = mcu.create_oid()
mcu.register_response(self._handle_spi_response,
"thermocouple_result", oid)
mcu.register_serial_response(
self._handle_spi_response,
"thermocouple_result oid=%c next_clock=%u value=%u fault=%c", oid)
mcu.register_config_callback(self._build_config)
def setup_minmax(self, min_temp, max_temp):
adc_range = [self.calc_adc(min_temp), self.calc_adc(max_temp)]

View file

@ -128,6 +128,25 @@ class CommandWrapper:
def get_command_tag(self):
return self._msgtag
# Wrapper for long-lived serial subscriptions (callbacks via background thread)
class AsyncResponseWrapper:
def __init__(self, conn_helper, cfg_helper, callback, msgformat, oid=None):
self._serial = conn_helper.get_serial()
self._callback = callback
self._msgformat = msgformat
self._name = msgformat.split()[0]
self._oid = oid
if cfg_helper.is_config_finalized():
self._register()
else:
self._serial.register_response((lambda p: None), self._name, oid)
cfg_helper.register_post_init_callback(self._register)
def _register(self):
self._serial.get_msgparser().lookup_command(self._msgformat)
self._serial.register_response(self._callback, self._name, self._oid)
def unregister(self):
self._serial.register_response(None, self._name, self._oid)
######################################################################
# Wrapper classes for MCU pins
@ -146,6 +165,7 @@ class MCU_trsync:
self._trdispatch_mcu = None
self._oid = mcu.create_oid()
self._cmd_queue = mcu.alloc_command_queue()
self._response_trsync = None
self._trsync_start_cmd = self._trsync_set_timeout_cmd = None
self._trsync_trigger_cmd = self._trsync_query_cmd = None
self._stepper_stop_cmd = None
@ -232,8 +252,10 @@ class MCU_trsync:
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_mcu_setup(self._trdispatch_mcu, clock, expire_clock,
expire_ticks, min_extend_ticks)
self._mcu.register_response(self._handle_trsync_state,
"trsync_state", self._oid)
self._response_trsync = self._mcu.register_serial_response(
self._handle_trsync_state,
"trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u",
self._oid)
self._trsync_start_cmd.send([self._oid, report_clock, report_ticks,
self.REASON_COMMS_TIMEOUT], reqclock=clock)
for s in self._steppers:
@ -243,7 +265,8 @@ class MCU_trsync:
def set_home_end_time(self, home_end_time):
self._home_end_clock = self._mcu.print_time_to_clock(home_end_time)
def stop(self):
self._mcu.register_response(None, "trsync_state", self._oid)
self._response_trsync.unregister()
self._response_trsync = None
self._trigger_completion = None
if self._mcu.is_fileoutput():
return self.REASON_ENDSTOP_HIT
@ -589,8 +612,9 @@ class MCU_adc:
self._oid, clock, sample_ticks, self._sample_count,
self._report_clock, min_sample, max_sample,
self._range_check_count), is_init=True)
self._mcu.register_response(self._old_handle_analog_in_state,
"analog_in_state", self._oid)
self._mcu.register_serial_response(
self._old_handle_analog_in_state,
"analog_in_state oid=%c next_clock=%u value=%hu", self._oid)
return
BYTES_PER_SAMPLE = 2
bytes_per_report = self._batch_num * BYTES_PER_SAMPLE
@ -601,8 +625,9 @@ class MCU_adc:
self._oid, clock, sample_ticks, self._sample_count,
self._report_clock, bytes_per_report, min_sample, max_sample,
self._range_check_count), is_init=True)
self._mcu.register_response(self._handle_analog_in_state,
"analog_in_state", self._oid)
self._mcu.register_serial_response(
self._handle_analog_in_state,
"analog_in_state oid=%c next_clock=%u values=%*s", self._oid)
def _old_handle_analog_in_state(self, params):
last_value = params['value'] * self._inv_max_adc
next_clock = self._mcu.clock32_to_clock64(params['next_clock'])
@ -852,9 +877,9 @@ class MCUConnectHelper:
logging.info(self.log_info())
# Setup shutdown handling
self._emergency_stop_cmd = self._mcu.lookup_command("emergency_stop")
self._mcu.register_response(self._handle_shutdown, 'shutdown')
self._mcu.register_response(self._handle_shutdown, 'is_shutdown')
self._mcu.register_response(self._handle_starting, 'starting')
self._serial.register_response(self._handle_shutdown, 'shutdown')
self._serial.register_response(self._handle_shutdown, 'is_shutdown')
self._serial.register_response(self._handle_starting, 'starting')
def _analyze_shutdown(self, msg, details):
if self._mcu.is_fileoutput():
return
@ -921,7 +946,7 @@ class MCUStatsHelper:
self._get_status_info['mcu_version'] = version
self._get_status_info['mcu_build_versions'] = build_versions
self._get_status_info['mcu_constants'] = msgparser.get_constants()
self._mcu.register_response(self._handle_mcu_stats, 'stats')
self._serial.register_response(self._handle_mcu_stats, 'stats')
def _ready(self):
if self._mcu.is_fileoutput():
return
@ -961,11 +986,14 @@ class MCUConfigHelper:
self._reactor = printer.get_reactor()
self._name = mcu.get_name()
# Configuration tracking
self._config_finalized = False
self._oid_count = 0
self._config_callbacks = []
self._post_init_callbacks = []
self._config_cmds = []
self._restart_cmds = []
self._init_cmds = []
self._config_crc = 0
self._mcu_freq = 0.
self._reserved_move_slots = 0
# Register handlers
@ -973,10 +1001,11 @@ class MCUConfigHelper:
printer.register_event_handler("klippy:mcu_identify",
self._mcu_identify)
printer.register_event_handler("klippy:connect", self._connect)
def _send_config(self, prev_crc):
def _finalize_config(self):
# Build config commands
for cb in self._config_callbacks:
cb()
self._config_finalized = True
self._config_cmds.insert(0, "allocate_oids count=%d"
% (self._oid_count,))
# Resolve pin names
@ -987,24 +1016,11 @@ class MCUConfigHelper:
cmdlist[i] = pin_resolver.update_command(cmd)
# Calculate config CRC
encoded_config = '\n'.join(self._config_cmds).encode()
config_crc = zlib.crc32(encoded_config) & 0xffffffff
self.add_config_cmd("finalize_config crc=%d" % (config_crc,))
if prev_crc is not None and config_crc != prev_crc:
restart_helper = self._conn_helper.get_restart_helper()
restart_helper.check_restart_on_crc_mismatch()
raise error("MCU '%s' CRC does not match config" % (self._name,))
# Transmit config messages (if needed)
self._config_crc = zlib.crc32(encoded_config) & 0xffffffff
self._config_cmds.append("finalize_config crc=%d" % (self._config_crc,))
def _send_cfg_init_commands(self, cmds):
try:
if prev_crc is None:
logging.info("Sending MCU '%s' printer configuration...",
self._name)
for c in self._config_cmds:
self._serial.send(c)
else:
for c in self._restart_cmds:
self._serial.send(c)
# Transmit init messages
for c in self._init_cmds:
for c in cmds:
self._serial.send(c)
except msgproto.enumeration_error as e:
enum_name, enum_value = e.get_enum_params()
@ -1029,22 +1045,36 @@ class MCUConfigHelper:
self._name,))
return config_params
def _connect(self):
# Finalize the config and check if a restart is needed
restart_helper = self._conn_helper.get_restart_helper()
config_params = self._send_get_config()
if not config_params['is_config']:
restart_helper = self._conn_helper.get_restart_helper()
# Not configured - sending full config will be required
restart_helper.check_restart_on_send_config()
# Not configured - send config and issue get_config again
self._send_config(None)
config_params = self._send_get_config()
if not config_params['is_config'] and not self._mcu.is_fileoutput():
raise error("Unable to configure MCU '%s'" % (self._name,))
self._finalize_config()
cfg_init_cmds = self._config_cmds + self._init_cmds
logging.info("Sending MCU '%s' printer configuration...",
self._name)
else:
# Already configured - may need to only send init commands
start_reason = self._printer.get_start_args().get("start_reason")
if start_reason == 'firmware_restart':
raise error("Failed automated reset of MCU '%s'"
% (self._name,))
# Already configured - send init commands
self._send_config(config_params['crc'])
self._finalize_config()
if self._config_crc != config_params['crc']:
restart_helper.check_restart_on_crc_mismatch()
raise error("MCU '%s' CRC does not match config"
% (self._name,))
cfg_init_cmds = self._restart_cmds + self._init_cmds
# Send config and init messages
self._send_cfg_init_commands(cfg_init_cmds)
config_params = self._send_get_config()
if not config_params['is_config'] and not self._mcu.is_fileoutput():
raise error("Unable to configure MCU '%s'" % (self._name,))
# Run post_init callbacks
for cb in self._post_init_callbacks:
cb()
# Setup steppersync with the move_count returned by get_config
move_count = config_params['move_count']
if move_count < self._reserved_move_slots:
@ -1072,25 +1102,37 @@ class MCUConfigHelper:
" to be able to resolve a maximum nominal duration"
" of %ds. Max possible duration: %ds"
% (self._name, MAX_NOMINAL_DURATION, max_possible))
def _verify_not_finalized(self):
if self._config_finalized:
raise error("Internal error! MCU already configured")
# Config creation helpers
def is_config_finalized(self):
return self._config_finalized
def setup_pin(self, pin_type, pin_params):
self._verify_not_finalized()
pcs = {'endstop': MCU_endstop,
'digital_out': MCU_digital_out, 'pwm': MCU_pwm, 'adc': MCU_adc}
if pin_type not in pcs:
raise pins.error("pin type %s not supported on mcu" % (pin_type,))
return pcs[pin_type](self._mcu, pin_params)
def create_oid(self):
self._verify_not_finalized()
self._oid_count += 1
return self._oid_count - 1
def register_config_callback(self, cb):
self._verify_not_finalized()
self._config_callbacks.append(cb)
def add_config_cmd(self, cmd, is_init=False, on_restart=False):
self._verify_not_finalized()
if is_init:
self._init_cmds.append(cmd)
elif on_restart:
self._restart_cmds.append(cmd)
else:
self._config_cmds.append(cmd)
def register_post_init_callback(self, cb):
self._verify_not_finalized()
self._post_init_callbacks.append(cb)
def get_query_slot(self, oid):
slot = self.seconds_to_clock(oid * .01)
t = int(self._mcu.estimated_print_time(self._reactor.monotonic()) + 1.5)
@ -1157,11 +1199,17 @@ class MCU:
return self.lookup_command(msgformat)
except self._serial.get_msgparser().error as e:
return None
# SerialHdl wrappers
def register_response(self, cb, msg, oid=None):
self._serial.register_response(cb, msg, oid)
def alloc_command_queue(self):
return self._serial.alloc_command_queue()
def register_serial_response(self, cb, msg, oid=None):
return AsyncResponseWrapper(self._conn_helper, self._config_helper,
cb, msg, oid)
def check_valid_response(self, msgformat):
try:
self._serial.get_msgparser().lookup_command(msgformat)
except self._serial.get_msgparser().error as e:
return False
return True
# MsgParser wrappers
def get_enumerations(self):
return self._serial.get_msgparser().get_enumerations()