kopia lustrzana https://github.com/jbruce12000/kiln-controller
114 wiersze
3.8 KiB
Python
114 wiersze
3.8 KiB
Python
import csv
|
||
import argparse
|
||
|
||
|
||
# Using the method described in "Ziegler–Nichols Tuning Method∗" by Vishakha Vijay Patel
|
||
# (https://www.ias.ac.in/article/fulltext/reso/025/10/1385-1397)
|
||
|
||
|
||
def line(a, b, x):
|
||
return a * x + b
|
||
|
||
|
||
def invline(a, b, y):
|
||
return (y - b) / a
|
||
|
||
|
||
def plot(xdata, ydata,
|
||
tangent_min, tangent_max, tangent_slope, tangent_offset,
|
||
lower_crossing_x, upper_crossing_x):
|
||
from matplotlib import pyplot
|
||
|
||
minx = min(xdata)
|
||
maxx = max(xdata)
|
||
miny = min(ydata)
|
||
maxy = max(ydata)
|
||
|
||
pyplot.scatter(xdata, ydata)
|
||
|
||
pyplot.plot([minx, maxx], [miny, miny], '--', color='purple')
|
||
pyplot.plot([minx, maxx], [maxy, maxy], '--', color='purple')
|
||
|
||
pyplot.plot(tangent_min[0], tangent_min[1], 'v', color='red')
|
||
pyplot.plot(tangent_max[0], tangent_max[1], 'v', color='red')
|
||
pyplot.plot([minx, maxx], [line(tangent_slope, tangent_offset, minx), line(tangent_slope, tangent_offset, maxx)], '--', color='red')
|
||
|
||
pyplot.plot([lower_crossing_x, lower_crossing_x], [miny, maxy], '--', color='black')
|
||
pyplot.plot([upper_crossing_x, upper_crossing_x], [miny, maxy], '--', color='black')
|
||
|
||
pyplot.show()
|
||
|
||
|
||
def calculate(filename, tangentdivisor, showplot):
|
||
# parse the csv file
|
||
xdata = []
|
||
ydata = []
|
||
filemintime = None
|
||
with open(filename) as f:
|
||
for row in csv.DictReader(f):
|
||
try:
|
||
time = float(row['pid_time'])
|
||
temp = float(row['pid_ispoint'])
|
||
if filemintime is None:
|
||
filemintime = time
|
||
|
||
xdata.append(time - filemintime)
|
||
ydata.append(temp)
|
||
except ValueError:
|
||
continue # just ignore bad values!
|
||
|
||
# gather points for tangent line
|
||
miny = min(ydata)
|
||
maxy = max(ydata)
|
||
midy = (maxy + miny) / 2
|
||
yoffset = int((maxy - miny) / tangentdivisor)
|
||
tangent_min = tangent_max = None
|
||
for i in range(0, len(xdata)):
|
||
rowx = xdata[i]
|
||
rowy = ydata[i]
|
||
|
||
if rowy >= (midy - yoffset) and tangent_min is None:
|
||
tangent_min = (rowx, rowy)
|
||
elif rowy >= (midy + yoffset) and tangent_max is None:
|
||
tangent_max = (rowx, rowy)
|
||
|
||
# calculate tangent line to the main temperature curve
|
||
tangent_slope = (tangent_max[1] - tangent_min[1]) / (tangent_max[0] - tangent_min[0])
|
||
tangent_offset = tangent_min[1] - line(tangent_slope, 0, tangent_min[0])
|
||
|
||
# determine the point at which the tangent line crosses the min/max temperaturess
|
||
lower_crossing_x = invline(tangent_slope, tangent_offset, miny)
|
||
upper_crossing_x = invline(tangent_slope, tangent_offset, maxy)
|
||
|
||
# compute parameters
|
||
L = lower_crossing_x - min(xdata)
|
||
T = upper_crossing_x - lower_crossing_x
|
||
|
||
# Magic Ziegler-Nicols constants ahead!
|
||
Kp = 1.2 * (T / L)
|
||
Ti = 2 * L
|
||
Td = 0.5 * L
|
||
Ki = Kp / Ti
|
||
Kd = Kp * Td
|
||
|
||
# outut to the user
|
||
print(f"Kp: {Kp} 1/Ki: {1/ Ki}, Kd: {Kd}")
|
||
|
||
if showplot:
|
||
plot(xdata, ydata,
|
||
tangent_min, tangent_max, tangent_slope, tangent_offset,
|
||
lower_crossing_x, upper_crossing_x)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser(description='Perform Ziegler-Nichols PID tuning')
|
||
parser.add_argument('csvfile', type=str, help="The CSV file to read from. Must contain two columns called pid_time (time in seconds) and pid_ispoint (observed temperature)")
|
||
parser.add_argument('--showplot', action='store_true', help="If set, also plot results (requires pyplot to be pip installed)")
|
||
parser.add_argument('--tangentdivisor', type=float, default=4, help="Adjust the tangent calculation to fit better. Must be >= 2.")
|
||
args = parser.parse_args()
|
||
|
||
if args.tangentdivisor < 2:
|
||
raise ValueError("tangentdivisor must be >= 2")
|
||
|
||
calculate(args.csvfile, args.tangentdivisor, args.showplot)
|