mirror of
https://github.com/Klipper3d/klipper.git
synced 2026-03-04 17:14:38 -07:00
Merge 8e4e42af8f into 7a44726492
This commit is contained in:
commit
5e803fba28
10 changed files with 112 additions and 58 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
128
klippy/mcu.py
128
klippy/mcu.py
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue