klippy: Add access methods and avoid peeking into the printer classes

Add get_reactor(), lookup_object(), lookup_module_objects(), and
set_rollover_info() to the main Printer class so that callers do not
need to peek into the class' members.  Similarly, add get_printer()
and get_name() methods to the ConfigWrapper class.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2018-01-19 22:22:17 -05:00
parent f0a754e496
commit 81013ba5c8
10 changed files with 103 additions and 85 deletions

View File

@ -1,6 +1,6 @@
# Code to configure miscellaneous chips
#
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import pins, mcu
@ -22,7 +22,7 @@ PIN_MIN_TIME = 0.100
class PrinterPin:
def __init__(self, printer, config):
self.printer = printer
self.is_pwm = 'pwm' in config.section.split()[0]
self.is_pwm = 'pwm' in config.get_name().split()[0]
if self.is_pwm:
self.mcu_pin = pins.setup_pin(printer, 'pwm', config.get('pin'))
hard_pwm = config.getint('hard_pwm', None, minval=1)
@ -40,7 +40,7 @@ class PrinterPin:
self.last_value_time = 0.
self.last_value = config.getfloat(
'value', 0., minval=0., maxval=self.scale) / self.scale
self.is_static = config.section.startswith('static_')
self.is_static = config.get_name().startswith('static_')
if self.is_static:
self.mcu_pin.setup_start_value(
self.last_value, self.last_value, True)
@ -48,13 +48,13 @@ class PrinterPin:
shutdown_value = config.getfloat(
'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale
self.mcu_pin.setup_start_value(self.last_value, shutdown_value)
self.gcode = printer.objects['gcode']
self.gcode = printer.lookup_object('gcode')
self.gcode.register_command("SET_PIN", self.cmd_SET_PIN,
desc=self.cmd_SET_PIN_help)
cmd_SET_PIN_help = "Set the value of an output pin"
def cmd_SET_PIN(self, params):
pin_name = self.gcode.get_str('PIN', params)
pin = self.printer.objects.get('pin ' + pin_name)
pin = self.printer.lookup_object('pin ' + pin_name, None)
if pin is not self:
if pin is None:
raise self.gcode.error("Pin not configured")
@ -64,7 +64,7 @@ class PrinterPin:
value = self.gcode.get_float('VALUE', params) / self.scale
if value == self.last_value:
return
print_time = self.printer.objects['toolhead'].get_last_move_time()
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
print_time = max(print_time, self.last_value_time + PIN_MIN_TIME)
if self.is_pwm:
if value < 0. or value > 1.:
@ -89,7 +89,7 @@ class PrinterMultiPin:
self.mcu_pins = []
def setup_pin(self, pin_params):
pin_name = pin_params['pin']
pin = self.printer.objects.get('multi_pin ' + pin_name)
pin = self.printer.lookup_object('multi_pin ' + pin_name, None)
if pin is not self:
if pin is None:
raise pins.error("multi_pin %s not configured" % (pin_name,))
@ -147,7 +147,7 @@ class PrinterServo:
self.angle_to_width = (self.max_width - self.min_width) / self.max_angle
self.width_to_value = 1. / SERVO_SIGNAL_PERIOD
self.last_value = self.last_value_time = 0.
self.gcode = printer.objects['gcode']
self.gcode = printer.lookup_object('gcode')
self.gcode.register_command("SET_SERVO", self.cmd_SET_SERVO,
desc=self.cmd_SET_SERVO_help)
def set_pwm(self, print_time, value):
@ -167,12 +167,12 @@ class PrinterServo:
cmd_SET_SERVO_help = "Set servo angle"
def cmd_SET_SERVO(self, params):
servo_name = self.gcode.get_str('SERVO', params)
servo = self.printer.objects.get('servo ' + servo_name)
servo = self.printer.lookup_object('servo ' + servo_name, None)
if servo is not self:
if servo is None:
raise self.gcode.error("Servo not configured")
return servo.cmd_SET_SERVO(params)
print_time = self.printer.objects['toolhead'].get_last_move_time()
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
if 'WIDTH' in params:
self.set_pulse_width(print_time,
self.gcode.get_float('WIDTH', params))

View File

@ -1,6 +1,6 @@
# Code for handling printer nozzle extruders
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
@ -24,7 +24,7 @@ class PrinterExtruder:
'max_extrude_cross_section', 4. * self.nozzle_diameter**2
, above=0.)
self.max_extrude_ratio = max_cross_section / self.filament_area
toolhead = printer.objects['toolhead']
toolhead = printer.lookup_object('toolhead')
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_e_velocity = config.getfloat(
'max_extrude_only_velocity', max_velocity * self.max_extrude_ratio
@ -248,17 +248,17 @@ def add_printer_objects(printer, config):
def get_printer_extruders(printer):
out = []
for i in range(99):
extruder = printer.objects.get('extruder%d' % (i,))
extruder = printer.lookup_object('extruder%d' % (i,), None)
if extruder is None:
break
out.append(extruder)
return out
def get_printer_heater(printer, name):
if name == 'heater_bed' and name in printer.objects:
return printer.objects[name]
if name == 'heater_bed':
return printer.lookup_object(name)
if name == 'extruder':
name = 'extruder0'
if name.startswith('extruder') and name in printer.objects:
return printer.objects[name].get_heater()
if name.startswith('extruder'):
return printer.lookup_object(name).get_heater()
raise printer.config_error("Unknown heater '%s'" % (name,))

View File

@ -1,6 +1,6 @@
# Printer fan support
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import extruder, pins
@ -35,7 +35,7 @@ class PrinterFan:
class PrinterHeaterFan:
def __init__(self, printer, config):
self.fan = PrinterFan(printer, config)
self.mcu = printer.objects['mcu']
self.mcu = printer.lookup_object('mcu')
heater = config.get("heater", "extruder0")
self.heater = extruder.get_printer_heater(printer, heater)
self.heater_temp = config.getfloat("heater_temp", 50.0)
@ -43,7 +43,8 @@ class PrinterHeaterFan:
self.fan_speed = config.getfloat(
"fan_speed", max_power, minval=0., maxval=max_power)
self.fan.mcu_fan.setup_start_value(0., max_power)
printer.reactor.register_timer(self.callback, printer.reactor.NOW)
reactor = printer.get_reactor()
reactor.register_timer(self.callback, reactor.NOW)
def callback(self, eventtime):
current_temp, target_temp = self.heater.get_temp(eventtime)
if not current_temp and not target_temp and not self.fan.last_fan_time:

View File

@ -1,6 +1,6 @@
# Parse gcode commands
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, re, logging, collections
@ -17,7 +17,7 @@ class GCodeParser:
self.printer = printer
self.fd = fd
# Input handling
self.reactor = printer.reactor
self.reactor = printer.get_reactor()
self.is_processing_data = False
self.is_fileinput = not not printer.get_start_args().get("debuginput")
self.fd_handle = None
@ -72,14 +72,14 @@ class GCodeParser:
self.is_printer_ready = True
self.gcode_handlers = self.ready_gcode_handlers
# Lookup printer components
self.toolhead = self.printer.objects.get('toolhead')
self.toolhead = self.printer.lookup_object('toolhead')
extruders = extruder.get_printer_extruders(self.printer)
if extruders:
self.extruder = extruders[0]
self.toolhead.set_extruder(self.extruder)
self.heaters = [ e.get_heater() for e in extruders ]
self.heaters.append(self.printer.objects.get('heater_bed'))
self.fan = self.printer.objects.get('fan')
self.heaters.append(self.printer.lookup_object('heater_bed', None))
self.fan = self.printer.lookup_object('fan', None)
if self.is_fileinput and self.fd_handle is None:
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
def reset_last_position(self):

View File

@ -1,6 +1,6 @@
# Printer heater support
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging, threading
@ -106,7 +106,7 @@ class PrinterHeater:
error = error
def __init__(self, printer, config):
self.printer = printer
self.name = config.section
self.name = config.get_name()
sensor_params = config.getchoice('sensor_type', Sensors)
self.sensor = sensor_params['class'](config, sensor_params)
self.min_temp = config.getfloat('min_temp', minval=0.)
@ -326,7 +326,7 @@ class ControlAutoTune:
midpoint_pos = sorted(cycle_times)[len(cycle_times)/2][1]
Kp, Ki, Kd = self.calc_pid(midpoint_pos)
logging.info("Autotune: final: Kp=%f Ki=%f Kd=%f", Kp, Ki, Kd)
gcode = self.heater.printer.objects['gcode']
gcode = self.heater.printer.lookup_object('gcode')
gcode.respond_info(
"PID parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n"
"To use these parameters, update the printer config file with\n"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python2
# Main code for host side printer firmware
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, ConfigParser, logging, time, threading
@ -52,8 +52,12 @@ class ConfigWrapper:
def __init__(self, printer, section):
self.printer = printer
self.section = section
def get_wrapper(self, parser, option, default
, minval=None, maxval=None, above=None, below=None):
def get_printer(self):
return self.printer
def get_name(self):
return self.section
def _get_wrapper(self, parser, option, default,
minval=None, maxval=None, above=None, below=None):
if (default is not self.sentinel
and not self.printer.fileconfig.has_option(self.section, option)):
return default
@ -84,17 +88,17 @@ class ConfigWrapper:
option, self.section, below))
return v
def get(self, option, default=sentinel):
return self.get_wrapper(self.printer.fileconfig.get, option, default)
return self._get_wrapper(self.printer.fileconfig.get, option, default)
def getint(self, option, default=sentinel, minval=None, maxval=None):
return self.get_wrapper(
return self._get_wrapper(
self.printer.fileconfig.getint, option, default, minval, maxval)
def getfloat(self, option, default=sentinel
, minval=None, maxval=None, above=None, below=None):
return self.get_wrapper(
return self._get_wrapper(
self.printer.fileconfig.getfloat, option, default
, minval, maxval, above, below)
def getboolean(self, option, default=sentinel):
return self.get_wrapper(
return self._get_wrapper(
self.printer.fileconfig.getboolean, option, default)
def getchoice(self, option, choices, default=sentinel):
c = self.get(option, default)
@ -144,6 +148,31 @@ class Printer:
self.mcus = []
def get_start_args(self):
return self.start_args
def get_reactor(self):
return self.reactor
def get_state_message(self):
return self.state_message
def add_object(self, name, obj):
if obj in self.objects:
raise self.config_error(
"Printer object '%s' already created" % (name,))
self.objects[name] = obj
def lookup_object(self, name, default=ConfigWrapper.sentinel):
if name in self.objects:
return self.objects[name]
if default is ConfigWrapper.sentinel:
raise self.config_error("Unknown config object '%s'" % (name,))
return default
def lookup_module_objects(self, module_name):
prefix = module_name + ' '
objs = [self.objects[n]
for n in sorted(self.objects) if n.startswith(prefix)]
if module_name in self.objects:
return [self.objects[module_name]] + objs
return objs
def set_rollover_info(self, name, info):
if self.bglogger is not None:
self.bglogger.set_rollover_info(name, info)
def _stats(self, eventtime, force_output=False):
toolhead = self.objects.get('toolhead')
if toolhead is None:
@ -158,8 +187,6 @@ class Printer:
out.append(m.stats(eventtime))
logging.info("Stats %.1f: %s", eventtime, ' '.join(out))
return eventtime + 1.
def add_object(self, name, obj):
self.objects[name] = obj
def _load_config(self):
self.fileconfig = ConfigParser.RawConfigParser()
config_file = self.start_args['config_file']
@ -173,7 +200,7 @@ class Printer:
config = ConfigWrapper(self, 'printer')
for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]:
m.add_printer_objects(self, config)
self.mcus = mcu.get_printer_mcus(self)
self.mcus = self.lookup_module_objects('mcu')
# Validate that there are no undefined parameters in the config file
valid_sections = { s: 1 for s, o in self.all_config_options }
for section in self.fileconfig.sections():
@ -237,8 +264,6 @@ class Printer:
except:
logging.exception("Unhandled exception during post run")
return run_result
def get_state_message(self):
return self.state_message
def invoke_shutdown(self, msg):
if self.is_shutdown:
return

View File

@ -1,6 +1,6 @@
# Interface to Klipper micro-controller code
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, zlib, logging, math
@ -409,7 +409,8 @@ class MCU:
def __init__(self, printer, config, clocksync):
self._printer = printer
self._clocksync = clocksync
self._name = config.section
self._reactor = printer.get_reactor()
self._name = config.get_name()
if self._name.startswith('mcu '):
self._name = self._name[4:]
# Serial port
@ -419,7 +420,7 @@ class MCU:
or self._serialport.startswith("/tmp/klipper_host_")):
baud = config.getint('baud', 250000, minval=2400)
self._serial = serialhdl.SerialReader(
printer.reactor, self._serialport, baud)
self._reactor, self._serialport, baud)
# Restarts
self._restart_method = 'command'
if baud:
@ -430,8 +431,7 @@ class MCU:
self._emergency_stop_cmd = None
self._is_shutdown = self._is_timeout = False
self._shutdown_msg = ""
if printer.bglogger is not None:
printer.bglogger.set_rollover_info(self._name, None)
printer.set_rollover_info(self._name, None)
# Config building
pins.get_printer_pins(printer).register_chip(self._name, self)
self._oid_count = 0
@ -482,7 +482,7 @@ class MCU:
logging.info("Attempting automated MCU '%s' restart: %s",
self._name, reason)
self._printer.request_exit('firmware_restart')
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000)
self._reactor.pause(self._reactor.monotonic() + 2.000)
raise error("Attempt MCU '%s' restart failed" % (self._name,))
def _connect_file(self, pace=False):
# In a debugging mode. Open debug output file and read data dictionary
@ -566,16 +566,15 @@ class MCU:
raise error("MCU '%s' CRC does not match config" % (self._name,))
move_count = config_params['move_count']
logging.info("Configured MCU '%s' (%d moves)", self._name, move_count)
if self._printer.bglogger is not None:
msgparser = self._serial.msgparser
info = [
"Configured MCU '%s' (%d moves)" % (self._name, move_count),
"Loaded MCU '%s' %d commands (%s / %s)" % (
self._name, len(msgparser.messages_by_id),
msgparser.version, msgparser.build_versions),
"MCU '%s' config: %s" % (self._name, " ".join(
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
self._printer.bglogger.set_rollover_info(self._name, "\n".join(info))
msgparser = self._serial.msgparser
info = [
"Configured MCU '%s' (%d moves)" % (self._name, move_count),
"Loaded MCU '%s' %d commands (%s / %s)" % (
self._name, len(msgparser.messages_by_id),
msgparser.version, msgparser.build_versions),
"MCU '%s' config: %s" % (self._name, " ".join(
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
self._printer.set_rollover_info(self._name, "\n".join(info))
self._steppersync = self._ffi_lib.steppersync_alloc(
self._serial.serialqueue, self._stepqueues, len(self._stepqueues),
move_count)
@ -663,18 +662,17 @@ class MCU:
def clock32_to_clock64(self, clock32):
return self._clocksync.clock32_to_clock64(clock32)
def pause(self, waketime):
return self._printer.reactor.pause(waketime)
return self._reactor.pause(waketime)
def monotonic(self):
return self._printer.reactor.monotonic()
return self._reactor.monotonic()
# Restarts
def _restart_arduino(self):
logging.info("Attempting MCU '%s' reset", self._name)
self.disconnect()
serialhdl.arduino_reset(self._serialport, self._printer.reactor)
serialhdl.arduino_reset(self._serialport, self._reactor)
def _restart_via_command(self):
reactor = self._printer.reactor
if ((self._reset_cmd is None and self._config_reset_cmd is None)
or not self._clocksync.is_active(reactor.monotonic())):
or not self._clocksync.is_active(self._reactor.monotonic())):
logging.info("Unable to issue reset command on MCU '%s'", self._name)
return
if self._reset_cmd is None:
@ -682,19 +680,19 @@ class MCU:
logging.info("Attempting MCU '%s' config_reset command", self._name)
self._is_shutdown = True
self.do_shutdown(force=True)
reactor.pause(reactor.monotonic() + 0.015)
self._reactor.pause(self._reactor.monotonic() + 0.015)
self.send(self._config_reset_cmd.encode())
else:
# Attempt reset via reset command
logging.info("Attempting MCU '%s' reset command", self._name)
self.send(self._reset_cmd.encode())
reactor.pause(reactor.monotonic() + 0.015)
self._reactor.pause(self._reactor.monotonic() + 0.015)
self.disconnect()
def _restart_rpi_usb(self):
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
self.disconnect()
chelper.run_hub_ctrl(0)
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.)
self._reactor.pause(self._reactor.monotonic() + 2.)
chelper.run_hub_ctrl(1)
def microcontroller_restart(self):
if self._restart_method == 'rpi_usb':
@ -773,20 +771,14 @@ def error_help(msg):
return ""
def add_printer_objects(printer, config):
mainsync = clocksync.ClockSync(printer.reactor)
reactor = printer.get_reactor()
mainsync = clocksync.ClockSync(reactor)
printer.add_object('mcu', MCU(printer, config.getsection('mcu'), mainsync))
for s in config.get_prefix_sections('mcu '):
printer.add_object(s.section, MCU(
printer, s, clocksync.SecondarySync(printer.reactor, mainsync)))
def get_printer_mcus(printer):
return [printer.objects[n] for n in sorted(printer.objects)
if n.startswith('mcu')]
printer, s, clocksync.SecondarySync(reactor, mainsync)))
def get_printer_mcu(printer, name):
mcu_name = name
if name != 'mcu':
mcu_name = 'mcu ' + name
if mcu_name not in printer.objects:
raise printer.config_error("Unknown MCU %s" % (name,))
return printer.objects[mcu_name]
if name == 'mcu':
return printer.lookup_object(name)
return printer.lookup_object('mcu ' + name)

View File

@ -234,7 +234,7 @@ def add_printer_objects(printer, config):
printer.add_object('pins', PrinterPins())
def get_printer_pins(printer):
return printer.objects['pins']
return printer.lookup_object('pins')
def setup_pin(printer, pin_type, pin_desc):
return get_printer_pins(printer).setup_pin(pin_type, pin_desc)

View File

@ -36,7 +36,7 @@ def lookup_enable_pin(printer, pin):
# Code storing the definitions for a stepper motor
class PrinterStepper:
def __init__(self, printer, config):
self.name = config.section
self.name = config.get_name()
if self.name.startswith('stepper_'):
self.name = self.name[8:]
self.need_motor_enable = True
@ -101,7 +101,7 @@ class PrinterHomingStepper(PrinterStepper):
else:
raise config.error(
"Unable to infer homing_positive_dir in section '%s'" % (
config.section,))
config.get_name(),))
# Endstop stepper phase position tracking
self.homing_stepper_phases = config.getint(
'homing_stepper_phases', None, minval=0)
@ -169,9 +169,9 @@ class PrinterMultiStepper(PrinterHomingStepper):
self.extras = []
self.all_step_const = [self.step_const]
for i in range(1, 99):
if not config.has_section(config.section + str(i)):
if not config.has_section(config.get_name() + str(i)):
break
extraconfig = config.getsection(config.section + str(i))
extraconfig = config.getsection(config.get_name() + str(i))
extra = PrinterStepper(printer, extraconfig)
self.extras.append(extra)
self.all_step_const.append(extra.step_const)
@ -202,6 +202,6 @@ class PrinterMultiStepper(PrinterHomingStepper):
return self.endstops
def LookupMultiHomingStepper(printer, config):
if not config.has_section(config.section + '1'):
if not config.has_section(config.get_name() + '1'):
return PrinterHomingStepper(printer, config)
return PrinterMultiStepper(printer, config)

View File

@ -1,6 +1,6 @@
# Code for coordinating events on the printer toolhead
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
@ -183,8 +183,8 @@ STALL_TIME = 0.100
class ToolHead:
def __init__(self, printer, config):
self.printer = printer
self.reactor = printer.reactor
self.all_mcus = mcu.get_printer_mcus(printer)
self.reactor = printer.get_reactor()
self.all_mcus = printer.lookup_module_objects('mcu')
self.mcu = self.all_mcus[0]
self.max_velocity = config.getfloat('max_velocity', above=0.)
self.max_accel = config.getfloat('max_accel', above=0.)