From 1abeac4f4ebe7a1f2be87fda8467b4091c8f430b Mon Sep 17 00:00:00 2001 From: jbruce Date: Thu, 3 Nov 2022 08:57:58 -0900 Subject: [PATCH] simplify kiln-tuner, fix docs --- README.md | 8 ++++++- config.py | 22 +++++++++--------- kiln-tuner.py | 63 +++++++++++++++++++++++++++++---------------------- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c9f62b3..b96c286 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,12 @@ If you're done playing around with simulations and want to deploy the code on a All parameters are defined in config.py, review/change to your mind's content. -You should change, test, and verify PID parameters in config.py. Here is a [PID Tuning Guide](https://github.com/jbruce12000/kiln-controller/blob/master/docs/pid_tuning.md). There is also an [autotuner](https://github.com/jbruce12000/kiln-controller/blob/master/docs/ziegler_tuning.md). Be patient with tuning. No tuning is perfect across a wide temperature range. +You should change, test, and verify PID parameters in config.py. You need to read through config.py carefully to understand each setting. You may want to change the configuration parameter **sensor_time_wait**. It's the duty cycle for the entire system. It's set to two seconds by default which means that a decision is made every 2s about whether to turn on relay[s] and for how long. If you use mechanical relays, you may want to increase this. At 2s, my SSR switches 11,000 times in 13 hours. +You should change **temp_scale** to either f for Farenheit or c for Celcius. + ## Testing After you've completed connecting all the hardware together, there are scripts to test the thermocouple and to test the output to the solid state relay. Read the scripts below and then start your testing. First, activate the virtual environment like so... @@ -116,6 +118,10 @@ and you can use this script to examine each pin's state including input/output/v $ ./gpioreadall.py +## PID Tuning + +Run the [autotuner](https://github.com/jbruce12000/kiln-controller/blob/master/docs/ziegler_tuning.md). It will heat your kiln to 400, pass that, and then once it cools back down to 400, it will calculate PID values to copy into config.py. No tuning is perfect across a wide temperature range. Here is a [PID Tuning Guide](https://github.com/jbruce12000/kiln-controller/blob/master/docs/pid_tuning.md) if you end up having to manually tune. + ## Usage ### Server Startup diff --git a/config.py b/config.py index 1015521..fb42577 100644 --- a/config.py +++ b/config.py @@ -28,7 +28,6 @@ listening_port = 8081 # This is used to calculate a cost estimate before a run. It's also used # to produce the actual cost during a run. My kiln has three # elements that when my switches are set to high, consume 9460 watts. - kwh_rate = 0.1319 # cost per kilowatt hour per currency_type to calculate cost to run job kw_elements = 9.460 # if the kiln elements are on, the wattage in kilowatts currency_type = "$" # Currency Symbol to show when calculating cost to run job @@ -99,10 +98,12 @@ sensor_time_wait = 2 # well with the simulated oven. You must tune them to work well with # your specific kiln. Note that the integral pid_ki is # inverted so that a smaller number means more integral action. -pid_kp = 25 # Proportional 25,200,200 -pid_ki = 10 # Integral -pid_kd = 200 # Derivative - +#pid_kp = 25 # Proportional 25,200,200 +#pid_ki = 10 # Integral +#pid_kd = 200 # Derivative +pid_kp = 14.22801211254364 +pid_ki = 4.747842807629315 +pid_kd = 240.283966775251 ######################################################################## # @@ -115,7 +116,7 @@ stop_integral_windup = True ######################################################################## # # Simulation parameters -simulate = False +simulate = True sim_t_env = 60.0 # deg C sim_c_heat = 500.0 # J/K heat capacity of heat element sim_c_oven = 5000.0 # J/K heat capacity of oven @@ -132,7 +133,6 @@ sim_R_ho_air = 0.05 # K/W " with internal air circulation # # If you change the temp_scale, all settings in this file are assumed to # be in that scale. - temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display time_scale_slope = "h" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile @@ -164,10 +164,10 @@ pid_control_window = 5 #degrees # cheap thermocouple. Invest in a better thermocouple. thermocouple_offset=0 -# number of samples of temperature to average. -# If you suffer from the high temperature kiln issue and have set -# honour_theromocouple_short_errors to False, -# you will likely need to increase this (eg I use 40) +# number of samples of temperature to average over each duty cycle. +# The larger the number, the more load on the board. K type +# thermocouples have a precision of about 1/2 degree C. This +# averaging smooths out the stair step jumps of this imprecision. temperature_average_samples = 40 # Thermocouple AC frequency filtering - set to True if in a 50Hz locale, else leave at False for 60Hz locale diff --git a/kiln-tuner.py b/kiln-tuner.py index 9ae3d6b..0591a7b 100755 --- a/kiln-tuner.py +++ b/kiln-tuner.py @@ -6,18 +6,19 @@ import csv import time import argparse -def recordprofile(csvfile, targettemp): - - try: +try: sys.dont_write_bytecode = True import config sys.dont_write_bytecode = False - except ImportError: +except ImportError: print("Could not import config file.") print("Copy config.py.EXAMPLE to config.py and adapt it for your setup.") exit(1) + +def recordprofile(csvfile, targettemp): + script_dir = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, script_dir + '/lib/') @@ -186,31 +187,39 @@ if __name__ == "__main__": subparsers = parser.add_subparsers() parser.set_defaults(mode='') - parser_profile = subparsers.add_parser('recordprofile', help='Record kiln temperature profile') - parser_profile.add_argument('csvfile', type=str, help="The CSV file to write to.") - parser_profile.add_argument('--targettemp', type=int, default=400, help="The target temperature to drive the kiln to (default 400).") - parser_profile.set_defaults(mode='recordprofile') + csvfile = "tuning.csv" + target = 400 + if config.temp_scale.lower() == "c": + target = (target - 32)*5/9 + tangentdivisor = 8 - parser_zn = subparsers.add_parser('zn', help='Calculate Ziegler-Nicols parameters') - parser_zn.add_argument('csvfile', type=str, help="The CSV file to read from. Must contain two columns called time (time in seconds) and temperature (observed temperature)") - parser_zn.add_argument('--showplot', action='store_true', help="If set, also plot results (requires pyplot to be pip installed)") - parser_zn.add_argument('--tangentdivisor', type=float, default=8, help="Adjust the tangent calculation to fit better. Must be >= 2 (default 8).") - parser_zn.set_defaults(mode='zn') + # parser_profile = subparsers.add_parser('recordprofile', help='Record kiln temperature profile') + # parser_profile.add_argument('csvfile', type=str, help="The CSV file to write to.") + # parser_profile.add_argument('--targettemp', type=int, default=400, help="The target temperature to drive the kiln to (default 400).") + # parser_profile.set_defaults(mode='recordprofile') + + #parser_zn = subparsers.add_parser('zn', help='Calculate Ziegler-Nicols parameters') + #parser_zn.add_argument('csvfile', type=str, help="The CSV file to read from. Must contain two columns called time (time in seconds) and temperature (observed temperature)") + #parser_zn.add_argument('--showplot', action='store_true', help="If set, also plot results (requires pyplot to be pip installed)") + #parser_zn.add_argument('--tangentdivisor', type=float, default=8, help="Adjust the tangent calculation to fit better. Must be >= 2 (default 8).") + #parser_zn.set_defaults(mode='zn') args = parser.parse_args() - if args.mode == 'recordprofile': - recordprofile(args.csvfile, args.targettemp) + # default behavior is to record profile to csv file tuning.csv + # and then calculate pid values and print them + recordprofile(csvfile, target) + calculate(csvfile, tangentdivisor, False) - elif args.mode == 'zn': - if args.tangentdivisor < 2: - raise ValueError("tangentdivisor must be >= 2") - - calculate(args.csvfile, args.tangentdivisor, args.showplot) - - elif args.mode == '': - parser.print_help() - exit(1) - - else: - raise NotImplementedError("Unknown mode %s" % args.mode) + #elif args.mode == 'zn': + # if args.tangentdivisor < 2: + # raise ValueError("tangentdivisor must be >= 2") + # + # calculate(args.csvfile, args.tangentdivisor, args.showplot) + # + # elif args.mode == '': + # parser.print_help() + # exit(1) + # + # else: + # raise NotImplementedError("Unknown mode %s" % args.mode)