Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

z_thermal_adjust: Add Z thermal adjuster module #4157

Merged
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
48ffbde
frame_expansion_compensation: Add thermal expansion compensation
alchemyEngine Apr 10, 2021
dcfc683
frame_expansion_compensation: key var to get_status(), docs formatting
alchemyEngine Apr 21, 2021
1a6a49d
frame_expansion_compensation: whitespace cleanup
alchemyEngine Apr 22, 2021
7e16f2b
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine May 10, 2021
66f2e23
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine May 15, 2021
6fa3841
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine May 28, 2021
b28d3a3
frame_expansion_compensation: minor code clean-up per comments
alchemyEngine May 28, 2021
a4bb2d1
docs: g-codes frame_expansion_compensation fix
alchemyEngine May 28, 2021
718055c
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine Jun 11, 2021
074b1bf
frame_expansion_compensation: temperature smoothing
alchemyEngine Jun 12, 2021
5a0c6e2
frame_expansion_compensation: consider step_distance (noise reduction)
alchemyEngine Jun 12, 2021
28f0e3e
frame_expansion_compensation: whitespace
alchemyEngine Jun 12, 2021
45336e7
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine Jun 24, 2021
66e16d5
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine Jul 6, 2021
2d3afd7
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine Jul 13, 2021
f76555a
Merge branch 'master' of https://github.com/KevinOConnor/klipper into…
alchemyEngine Jul 28, 2021
9d906a6
Merge branch 'KevinOConnor-master' into work-frame-expansion-20210410
alchemyEngine Jul 28, 2021
c5cdcdd
frame_expansion_compensation: Use independantly defined temp sensor
alchemyEngine Aug 1, 2021
79818f5
frame_expansion_compensation: Whitespace removal
alchemyEngine Aug 1, 2021
810f2d7
docs: frame_expansion_compensation sensor update
alchemyEngine Aug 1, 2021
5c6ae33
frame_expansion_compensation: Use temp_coeff for calcs
alchemyEngine Aug 1, 2021
5a24e05
docs: frame_expansion_compensation update
alchemyEngine Aug 1, 2021
e7c30c7
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine Aug 18, 2021
f69100f
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine Aug 26, 2021
af9cb99
frame_expansion_compensation: fix bouncy Z correction
alchemyEngine Aug 28, 2021
9e230eb
Merge branch 'KevinOConnor:master' into work-frame-expansion-20210410
alchemyEngine Sep 9, 2021
e3814ad
frame_expansion_compensation: set_frame_ref_temp
alchemyEngine Sep 16, 2021
9f38d24
frame_expansion_compensation: fix self typo
alchemyEngine Sep 16, 2021
38f8caa
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Oct 8, 2021
748f56f
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Oct 15, 2021
1e8536e
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Oct 23, 2021
9e584b0
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Nov 2, 2021
4d7443a
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Nov 7, 2021
3ea8e49
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Nov 17, 2021
04f443d
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Nov 27, 2021
974ab9c
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Dec 17, 2021
bc3f358
frame_expansion_compensation: deprecate branch
alchemyEngine Dec 19, 2021
ebc3b74
frame_expansion_compensation: white space
alchemyEngine Dec 19, 2021
6cf5117
frame_expansion_compensation: fix deprecation warning mesage
alchemyEngine Dec 26, 2021
3b63ebb
frame_expansion_compensation: whitespace again
alchemyEngine Dec 26, 2021
8079da2
docs: frame_expansion_compensation update
alchemyEngine Mar 4, 2022
8f405d9
frame_expansion_compensation: drop legacy config support
alchemyEngine Mar 5, 2022
65dc088
frame_expansion_compensation: remove deprecation warning
alchemyEngine Mar 5, 2022
97f6a9a
frame_expansion_compensation: manaully set reference temp
alchemyEngine Mar 5, 2022
0ea7ae7
frame_expansion_compensation: add set temp_coeff via gcode
alchemyEngine Mar 5, 2022
702bc2d
frame_expansion_compensation: remove z_stepper config parameter
alchemyEngine Mar 5, 2022
fb2d1f7
frame_expansion_compensation: simplify state reporting
alchemyEngine Mar 6, 2022
d341285
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Mar 10, 2022
3530062
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Mar 15, 2022
a6ccf0d
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Mar 20, 2022
61cdfb8
z_thermal_adjust: renamed from frame_expansion_compensation
alchemyEngine Mar 20, 2022
2bdbea5
docs: update z_thermal_adjust g-codes
alchemyEngine Mar 20, 2022
a14140a
docs: update z_thermal_adjust config reference
alchemyEngine Mar 20, 2022
79f0878
docs: add z_thermal_adjust status reference
alchemyEngine Mar 20, 2022
a1599d6
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Apr 6, 2022
35affe2
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Apr 21, 2022
d654d7e
docs: revert unintended heading change
alchemyEngine May 27, 2022
c183076
docs: z_thermal_adjust unit consistency
alchemyEngine May 27, 2022
cf82988
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Jun 4, 2022
60c5e11
z_thermal_adjust: consolidate gcode commands
alchemyEngine Jun 7, 2022
0f36462
Merge branch 'work-frame-expansion-20210410' of https://github.com/al…
alchemyEngine Jun 7, 2022
161202b
docs: update z_thermal_adjust g-codes
alchemyEngine Jun 7, 2022
62398a1
docs: fix z_thermal_adjust whitespace
alchemyEngine Jun 7, 2022
ec01c0b
docs: fix z_thermal_adjust quotes
alchemyEngine Jun 7, 2022
40eb6c4
z_thermal_adjust: reimplement internal temperature sensor
alchemyEngine Jun 15, 2022
2233448
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Jul 30, 2022
e6deb42
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Aug 25, 2022
af928bb
z_thermal_adjust: track last_position in get_position
alchemyEngine Aug 25, 2022
d5e21e2
z_thermal_adjust: fix last_position tracking in get_position
alchemyEngine Aug 25, 2022
c78de87
docs: update config_reference for z_thermal_adjust
alchemyEngine Aug 25, 2022
2ace087
docs: update status_reference for z_thermal_adjust
alchemyEngine Aug 25, 2022
84dfa0b
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Sep 8, 2022
e8b786d
Merge branch 'Klipper3d:master' into work-frame-expansion-20210410
alchemyEngine Sep 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,40 @@ the nature of skew correction these lengths are set via gcode. See
[skew_correction]
```

## Customized homing
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this makes an unintended change to this section header.

### [z_thermal_adjust]

Temperature-dependant toolhead Z position adjustment. Compensate for vertical
toolhead movement caused by thermal expansion of the printer's frame in
real-time using a temperature sensor (typically coupled to a vertical section
of frame).

See also: [extended g-code commands](G-Codes.md#z_thermal_adjust).

```
[z_thermal_adjust]
#temp_coeff:
# The temperature coefficient of expansion, in mm/K. For example, a
# temp_coeff of 0.01 mm/K will move the Z axis downwards by 0.01 mm for every
# Kelvin/degree Celsius that the temperature sensor increases. Defaults to
# 0.0, which applies no adjustment.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use only Celsius in the description. I find it very confusing to use different units for different settings, so the convention in Klipper is for distances to always be in mm, temperatures in Celsius, time in seconds, etc. I understand Kelvin and Celsius are interchangeable in this context, but for consistency I think the documentation should only use Celsius.

temp_sensor:
# Temperature sensor to use for Z adjustment. Use full config section name
# without quoutes. E.g. temperature_sensor frame. Also compatible with
# temperature_fan sensors. This parameter must be provided.
#smooth_time:
# Smoothing window applied to the temperature sensor, in seconds. Can reduce
# motor noise from excessive small corrections in response to sensor noise.
# The default is 2.0 seconds.
#z_adjust_off_above:
# Disables adjustments above this Z height [mm]. The last computed correction
# will remain applied until the toolhead moves below the specified Z height
# again. The default is 99999999.0 mm (always on).
#max_z_adjustment:
# Maximum absolute adjustment that can be applied to the Z axis [mm]. The
# default is 99999999.0 mm (unlimited).
```

# Customized homing

### [safe_z_home]

Expand Down
24 changes: 24 additions & 0 deletions docs/G-Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,30 @@ print.
#### SDCARD_RESET_FILE
`SDCARD_RESET_FILE`: Unload file and clear SD state.

### [z_thermal_adjust]

The following commands are available when the
[z_thermal_adjust config section](Config_Reference.md#z_thermal_adjust)
is enabled.

#### QUERY_Z_THERMAL_ADJUST
`QUERY_FRAME_COMP`: reports current state and key parameters of the Z Thermal
Adjust module.

#### SET_Z_THERMAL_ADJUST
`SET_Z_THERMAL_ADJUST [ENABLE=<0:1>] [COEFF=<value>]`: Enable or disable the
Z thermal adjustment with `ENABLE`. Disabling does not remove any adjustment
already applied, but will freeze the current adjustment value - this prevents
potentially unsafe downward Z movement. Re-enabling can potentially cause upward
tool movement as the adjustment is updated and applied. `COEFF` allows run-time
tuning of the adjustment temperature coefficient (i.e. the `TEMP_COEFF` config
parameter). `COEFF` values are not saved to the config.

#### SET_Z_THERMAL_ADJUST_REF
`SET_Z_THERMAL_ADJUST_REF [TEMPERATURE=<value>]`: Set the thermal adjustment
reference temperature to the current reading, or to the value supplied with
`TEMPERATURE`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be preferable to use a single command for all three of the above - SET_Z_THERMAL_ADJUST with no parameters just reports the current state, SET_Z_THERMAL_ADJUST with a REFERENCE_TEMPERATURE=123.4 sets the reference temperature, SET_Z_THERMAL_ADJUST with COEFF=567.8 sets the coefficient, etc.


### [z_tilt]

The following commands are available when the
Expand Down
11 changes: 11 additions & 0 deletions docs/Status_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,17 @@ object is always available):
- `state_message`: A human readable string giving additional context
on the current Klipper state.

## z_thermal_adjust

The following information is available in the `z_thermal_adjust` object (this
object is available if [z_thermal_adjust](Config_Reference.md#z_thermal_adjust)
is defined).
- `enabled`: Returns True if adjustment is enabled.
- `temperature`: Current (smoothed) temperature of the defined sensor. [degC]
- `current_z_adjust`: Last computed Z adjustment [mm].
- `z_adjust_ref_temperature`: Current reference temperature used for calculation
of Z `current_z_adjust` [degC].

## z_tilt

The following information is available in the `z_tilt` object (this
Expand Down
195 changes: 195 additions & 0 deletions klippy/extras/z_thermal_adjust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Z Thermal Adjust
#
# Copyright (C) 2022 Robert Pazdzior <robertp@norbital.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.

# Adjusts Z position in real-time using a thermal probe to e.g. compensate
# for thermal expansion of the printer frame.

import threading

class ZThermalAdjuster:
def __init__(self, config):
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
self.lock = threading.Lock()
self.config = config

# Get config parameters, convert to SI units where necessary
self.temp_coeff = config.getfloat('temp_coeff', minval=-1, maxval=1,
default=0)
self.temp_sensor_name = config.get('temp_sensor')
self.smooth_time = config.getfloat('smooth_time', 2., above=0.)
self.inv_smooth_time = 1. / self.smooth_time

self.off_above_z = config.getfloat('z_adjust_off_above', 99999999.)
self.max_z_adjust_mm = config.getfloat('max_z_adjustment', 99999999.)

# Register printer events
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.printer.register_event_handler("homing:home_rails_end",
self.handle_homing_move_end)

# Setup temperature sensor
self.last_temp = self.smoothed_temp = 0.
self.last_temp_time = 0.
self.ref_temperature = 0.

# Z transformation
self.z_adjust_mm = 0.
self.last_z_adjust_mm = 0.
self.adjust_enable = True
self.last_position = [0., 0., 0., 0.]
self.next_transform = None

# Register gcode commands
self.gcode.register_command('SET_Z_THERMAL_ADJUST',
self.cmd_SET_Z_THERMAL_ADJUST,
desc=self.cmd_SET_Z_THERMAL_ADJUST_help)
self.gcode.register_command('QUERY_Z_THERMAL_ADJUST',
self.cmd_QUERY_Z_THERMAL_ADJUST,
desc=self.cmd_QUERY_Z_THERMAL_ADJUST_help)
self.gcode.register_command('SET_Z_THERMAL_ADJUST_REF',
self.cmd_SET_Z_THERMAL_ADJUST_REF,
desc=self.cmd_SET_Z_THERMAL_ADJUST_REF_help)

def handle_connect(self):
'Called after all printer objects are instantiated'
self.toolhead = self.printer.lookup_object('toolhead')
gcode_move = self.printer.lookup_object('gcode_move')

# Temperature sensor config check and callback registration
try:
self.sensor = self.printer.lookup_object(self.temp_sensor_name)
except Exception as e:
msg = '''
%s\nUse the full config section name in the "temp_sensor" parameter,
e.g. "temp_sensor: temperature_sensor frame".
''' % e
raise self.printer.config_error(msg)
else:
self.sensor.sensor.setup_callback(self.z_adj_temperature_callback)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not valid to "peak" into another class like this (see docs/Code_Overview.md ). I don't undersand why this is being done. Shouldn't the code instantiate a new temperature sensor like other temperature modules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I legit forgot that I did this. It's a workaround; I originally implemented it as you described, but people complained they couldn't see it in their front end, use it with a temperature_fan, etc. I think duplicate_pin_override might be a viable workaround for those people now, no need to do it in here.


# Register move transformation
self.next_transform = gcode_move.set_move_transform(self, force=True)

# Pull Z step distance for minimum adjustment increment
kin = self.printer.lookup_object('toolhead').get_kinematics()
steppers = [s.get_name() for s in kin.get_steppers()]
z_stepper = kin.get_steppers()[steppers.index("stepper_z")]
self.z_step_dist = z_stepper.get_step_dist()

def get_status(self, eventtime):
return {
'temperature': self.smoothed_temp,
'current_z_adjust': self.z_adjust_mm,
'z_adjust_ref_temperature': self.ref_temperature,
'enabled': self.adjust_enable
}

def handle_homing_move_end(self, homing_state, rails):
'Set reference temperature after Z homing.'
if 2 in homing_state.get_axes():
self.ref_temperature = self.smoothed_temp
self.z_adjust_mm = 0.

def calc_adjust(self, pos):
'Z adjustment calculation'
if pos[2] < self.off_above_z:
delta_t = self.smoothed_temp - self.ref_temperature

# Calculate Z adjustment
adjust = -1 * self.temp_coeff * delta_t

# compute sign (+1 or -1) for maximum offset setting
sign = 1 - (adjust <= 0)*2

# Don't apply adjustments smaller than step distance
if abs(adjust - self.z_adjust_mm) > self.z_step_dist:
self.z_adjust_mm = min([self.max_z_adjust_mm*sign,
adjust], key=abs)

# Apply Z adjustment
new_z = pos[2] + self.z_adjust_mm
self.last_z_adjust_mm = self.z_adjust_mm
return [pos[0], pos[1], new_z, pos[3]]

def calc_unadjust(self, pos):
'Remove Z adjustment'
unadjusted_z = pos[2] - self.z_adjust_mm
return [pos[0], pos[1], unadjusted_z, pos[3]]

def get_position(self):
return self.calc_unadjust(self.next_transform.get_position())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused why this doesn't set self.last_position?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgive my ignorance, is that just an expected property for a module like this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A gcode transform must be able to both translate to a new coordinate system (move()) and report the current position in the old coordinate system (get_position()). If some external process changes the position outside of a normal gcode move() call, then the system arranges for get_postiion() to be called so that all components in the gcode stream know the current position.

It seems the code is tracking a last_position, but it isn't resetting it on a get_position() call. That seems suspect to me.

-Kevin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation, Kevin!


def move(self, newpos, speed):
# don't apply to extrude only moves or when disabled
if (newpos[0:2] == self.last_position[0:2]) or not self.adjust_enable:
z = newpos[2] + self.last_z_adjust_mm
adjusted_pos = [newpos[0], newpos[1], z, newpos[3]]
self.next_transform.move(adjusted_pos, speed)
else:
adjusted_pos = self.calc_adjust(newpos)
self.next_transform.move(adjusted_pos, speed)
self.last_position[:] = newpos

def z_adj_temperature_callback(self, read_time, temp):
'Called everytime the thermistor is read, used for smoothing'
self.sensor.temperature_callback(read_time, temp)
with self.lock:
time_diff = read_time - self.last_temp_time
self.last_temp = temp
self.last_temp_time = read_time
temp_diff = temp - self.smoothed_temp
adj_time = min(time_diff * self.inv_smooth_time, 1.)
self.smoothed_temp += temp_diff * adj_time

def cmd_SET_Z_THERMAL_ADJUST(self, gcmd):
new_state = gcmd.get_int('ENABLE', 1, minval=0, maxval=1)
self.temp_coeff = gcmd.get_float('COEFF', self.temp_coeff,
minval=-1, maxval=1)
state = 'ENABLED' if new_state else 'DISABLED'
msg = ("state: %s\n"
"temp_coeff: %f mm/K"
% (state, self.temp_coeff)
)
gcmd.respond_info(msg)

if new_state != self.adjust_enable:
gcode_move = self.printer.lookup_object('gcode_move')
gcode_move.reset_last_position()
self.adjust_enable = new_state

def cmd_QUERY_Z_THERMAL_ADJUST(self, gcmd):
state = 'ENABLED' if self.adjust_enable else 'DISABLED'
msg = ("state: %s\n"
"current temperature: %.2f degC\n"
"reference temperature: %.2f degC\n"
"applied Z adjustment: %.4f mm" % (state,
self.smoothed_temp,
self.ref_temperature,
self.z_adjust_mm)
)
gcmd.respond_info(msg)

def cmd_SET_Z_THERMAL_ADJUST_REF(self, gcmd):
ref_temp = gcmd.get_float('TEMPERATURE', -273.16, minval=-273.15)
if ref_temp > -273.16:
self.ref_temperature = ref_temp
else:
self.ref_temperature = self.smoothed_temp
msg = ("Z thermal adjust reference temperature set"
" manually to %.3fc" % self.ref_temperature)
gcmd.respond_info(msg)

cmd_SET_Z_THERMAL_ADJUST_help = 'Set Z Thermal Adjust parameters.'
cmd_QUERY_Z_THERMAL_ADJUST_help = ('Report current Z Thermal Adjust'
'parameters.')
cmd_SET_Z_THERMAL_ADJUST_REF_help = ('Use current probe temperature as Z '
'Thermal Adjust reference '
'temperature')

def load_config(config):
return ZThermalAdjuster(config)