-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathzero_overshoot.py
143 lines (134 loc) · 7.2 KB
/
zero_overshoot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# Calibration of heater PID settings
#
# Copyright (C) 2016-2018 Kevin O'Connor <[email protected]>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
from .pid_calibrate import (
PIDCalibrate,
ControlAutoTune
)
######################################################################
# Heater lookup and define new options for PID control
######################################################################
class ZeroOvershootExistingHeater:
def __init__(self, config):
if len(config.get_name().split()) > 2:
raise config.error(
"Name of section '%s' contains illegal whitespace"
% (config.get_name()))
self.full_section = config.get_name()
self.heater_name = self.full_section.split()[1]
self.section = self.full_section.split()[0]
self.zero_overshoot = config.getboolean('zero_overshoot', True)
self.Kp_multiplier = config.getfloat('Kp_multiplier', 1.)
self.Ki_multiplier = config.getfloat('Ki_multiplier', 1.)
self.Kd_multiplier = config.getfloat('Kd_multiplier', 1.)
# let Log file know this extension is active
logging.info("zero_overshoot ::INFO:: [zero_overshoot %s]: zero_overshoot = %s" % (self.heater_name, self.zero_overshoot))
logging.info("zero_overshoot ::INFO:: [zero_overshoot %s]: Kp_multiplier = %s" % (self.heater_name, self.Kp_multiplier))
logging.info("zero_overshoot ::INFO:: [zero_overshoot %s]: Ki_multiplier = %s" % (self.heater_name, self.Ki_multiplier))
logging.info("zero_overshoot ::INFO:: [zero_overshoot %s]: Kd_multiplier = %s" % (self.heater_name, self.Kd_multiplier))
def get_K_multiplier_values(self):
return self.zero_overshoot, self.Kp_multiplier, self.Ki_multiplier, self.Kd_multiplier
######################################################################
# ZERO_OVERSHOOT Klipper extension inherits from pid_calibrate.PIDCalibrate
######################################################################
class ZeroOvershootPIDCalibrate(PIDCalibrate):
def __init__(self, config):
self.printer = printer = config.get_printer()
self.heaters = {}
# Unregister any pre-existing pid_calibrate commands first.
gcode = self.printer.lookup_object('gcode')
gcode.register_command('PID_CALIBRATE', None)
PIDCalibrate.__init__(self, config)
self.printer.register_event_handler("klippy:ready", self.handle_ready)
gcode.register_command('HEATERS_STATUS', self.cmd_HEATERS_STATUS,
desc=self.cmd_HEATERS_STATUS_help)
def handle_ready(self):
# This is the hacky bit:
# The "klippy:ready" event is sent after all configuration has been
# read and all objects created. So, we are sure that the 'pid_calibrate'
# object already exists.
# So, we can reach into the printer objects and replace the existing 'pid_calibrate'
# object with this one.
pid_calibrate_obj = self.printer.objects.pop('pid_calibrate', None)
self.printer.objects['pid_calibrate'] = self
del pid_calibrate_obj
def add_existing_heater(self, config):
heater_name = config.get_name().split()[-1]
if not heater_name in ['extruder','heater_bed']:
raise config.error("Invalid Heater name: %s" % (heater_name,))
self.heaters[heater_name] = existing_heater = ZeroOvershootExistingHeater(config)
return existing_heater
def cmd_PID_CALIBRATE(self, gcmd):
heater_name = gcmd.get('HEATER')
pheaters = self.printer.lookup_object('heaters')
try:
heater = pheaters.lookup_heater(heater_name)
except self.printer.config_error as e:
raise gcmd.error(str(e))
zheater = self.heaters[heater_name]
zero_overshoot = zheater.get_K_multiplier_values()[0]
cmdline_param = gcmd.get_int("ZERO_OVERSHOOT", zero_overshoot)
target = gcmd.get_float('TARGET')
write_file = gcmd.get_int('WRITE_FILE', 0)
self.printer.lookup_object('toolhead').get_last_move_time()
calibrate = ControlAutoTune(heater, target)
old_control = heater.set_control(calibrate)
try:
pheaters.set_temperature(heater, target, True)
except self.printer.command_error as e:
heater.set_control(old_control)
raise
heater.set_control(old_control)
if write_file:
calibrate.write_file('/tmp/heattest.txt')
if calibrate.check_busy(0., 0., 0.):
raise gcmd.error("pid_calibrate interrupted")
Kp, Ki, Kd = calibrate.calc_final_pid()
logging.info("ClassicPID Autotune: final: Kp=%f Ki=%f Kd=%f" % (Kp, Ki, Kd))
#ZERO_OVERSHOOT zero_overshoot_final_calc func changes Kp, Ki, Kd values
Kp, Ki, Kd = self.zero_overshoot_final_calc(gcmd, heater_name, Kp, Ki, Kd, cmdline_param)
# Log and report results
if cmdline_param == True:
logging.info("Zero Overshoot Autotune: final: Kp=%f Ki=%f Kd=%f" % (Kp, Ki, Kd))
gcmd.respond_info(
"Zero Overshoot Calculated PID parameters: "
" pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
"with these parameters and restart the printer." % (Kp, Ki, Kd))
else:
logging.info("Autotune: final: Kp=%f Ki=%f Kd=%f" % (Kp, Ki, Kd))
gcmd.respond_info(
"PID parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
"with these parameters and restart the printer." % (Kp, Ki, Kd))
# Store results for SAVE_CONFIG
configfile = self.printer.lookup_object('configfile')
configfile.set(heater_name, 'control', 'pid')
configfile.set(heater_name, 'pid_Kp', "%.3f" % (Kp,))
configfile.set(heater_name, 'pid_Ki', "%.3f" % (Ki,))
configfile.set(heater_name, 'pid_Kd', "%.3f" % (Kd,))
def zero_overshoot_final_calc(self, gcmd, heater_name, Kp, Ki, Kd, cmdparam=None):
if cmdparam == False:
gcmd.respond_info("Using ClassicPID values...")
return Kp, Ki, Kd
gcmd.respond_info("Using Zero_Overshoot Calculated values...")
zheater = self.heaters[heater_name]
self.Kp_multiplier, self.Ki_multiplier, self.Kd_multiplier = zheater.get_K_multiplier_values()[1:]
myKp = round((Kp * self.Kp_multiplier),3)
myKi = round((Ki * self.Ki_multiplier),3)
myKd = round((Kd * self.Kd_multiplier),3)
return myKp, myKi, myKd
cmd_HEATERS_STATUS_help = "Get all available heaters and status"
def cmd_HEATERS_STATUS(self, gcmd):
pheaters = self.printer.lookup_object('heaters')
time = self.printer.lookup_object('toolhead').get_last_move_time()
pheater_status = pheaters.get_status(time)
gcmd.respond_info("Printer Heaters status %s" % (pheater_status))
def load_config(config):
return ZeroOvershootPIDCalibrate(config)
def load_config_prefix(config):
zheaters = config.get_printer().load_object(config, 'zero_overshoot')
return zheaters.add_existing_heater(config)