3

I'm running a simple PID control program in python. Basically an infinite while loop, which reads from sensors then calculates the appropriate control signal, as well as outputs diagnostic info to the terminal.

However, sometimes while watching the diagnostic info, I'd like to change the PID coefficients - which are essentially some constants used by the loop - by breaking from the loop, accepting user input, then returning to the very same loop. I'd like to do this an arbitrary number of times.

With 'goto' this would be simple and easy and just what I want. Can someone give me some python pseudo-code to do this? I can't really think of how to do it. I can interrupt the loop with a CTRL+C exception handler, but then I can't get back to the main loop.

There must be some very simple way to do this but I can't think of it. Thoughts? Snippets from my code:

while True:
        t0 = get_temp_deg_c(thermocouple1)
        print "Hose Temperature = " + str(t0) + " deg C"

        t1 = get_temp_deg_c(thermocouple2)
        print "Valve Temperature = " + str(t1) + " deg C"

        # write temps to file
        fi.write(str(t0))
        fi.write(" " + str(t1) + "\n")

        error = setpoint - t0

        print "Setpoint = " + str(setpoint) + " deg C"
        print "Error = " + str(error) + " deg C"

        percent_error = error/setpoint*100
        print "Percent error = " + str(percent_error) + " %"

        duty_out = p.GenOut(percent_error)
        print "PID controller duty output: " + str(duty_out) + " %"
        # clamp the output
        if(duty_out) > 100:
            duty_out = 100
        if(duty_out < 0):
            duty_out = 0

        PWM.set_duty_cycle(PWM_pin, duty_out)

        # do we need to increment the setpoint?
        if( (setpoint - setpoint_precision) ... # omitted logic here

        # Here we return to the top
Jonas
  • 121,568
  • 97
  • 310
  • 388
Paul L
  • 131
  • 3
  • Are you able modify the code to run the main loop in a thread? – metatoaster Oct 22 '15 at 05:29
  • Maybe threading the while loop would do the work, but I am not sure about whether threading allows outside thread references. Would you give us an actual code example? – Gabriel S. Gusmão Oct 22 '15 at 05:37
  • You can build in interactive shell of sort that will let users poke at variables inside your program while the pid loop runs in a thread, [an example that will let you also throw the whole thing into the background](http://stackoverflow.com/questions/32899275/python-script-hanging-when-running-in-the-background/32899461#32899461) – metatoaster Oct 22 '15 at 05:39
  • Basically it seems like you want to poll the keyboard's input buffer? This answer mentions some ways of doing it. http://stackoverflow.com/questions/292095/polling-the-keyboard-in-python – James Wang Oct 22 '15 at 05:40
  • Hi all, added a code snippet that contains the essentials. – Paul L Oct 22 '15 at 05:45

3 Answers3

1

As long as you're okay with restarting "from the top" after each interrupt (as opposed to returning to the exact point in the loop when the signal was raised, which is a much harder problem):

while True:
    try:
        controller.main_loop()
    except KeyboardInterrupt:
        controller.set_coefficients()
tzaman
  • 46,925
  • 11
  • 90
  • 115
0

In case you don't want a separate thread for IO, generators may be used to preserve the state of your loop across KeyboardInterrupts.

some_parameter = 1

def do_pid_stuff():
    while True:
        sensor_data1 = 'data'
        sensor_data2 = 'data'
        sensor_data3 = 'data'
        yield 'based on sensor_data1 ' * some_parameter
        yield 'based on sensor_data2 ' * some_parameter
        yield 'based on sensor_data3 ' * some_parameter


stuff = do_pid_stuff()
while True:
    try:
        for control_signal in stuff:
            print(control_signal)
    except KeyboardInterrupt:
        some_parameter = int(input())

So the main loop will continue with new parameters from the last executed yield. This however would require to rewrite your loop. Probably, it should be splitted into a generator that will give you sensor data and into a function that will actually do stuff based on the sensor values.

u354356007
  • 3,205
  • 15
  • 25
0

You already have a few ways to interact with your loop, I'd like to point out another one: select(). Using select(), you can wait for either user input. If you add a timeout, you can then break into the normal loop if no user input is available and interact with your hardware there.

Notes:

  • Here's the documentation for select , but consider the warning on top and look at the selectors module instead.
  • This solution, like the one using a keyboard interrupt, will stop interacting with the hardware while parameters are being changed. If that isn't acceptable, using a background thread is necessary.
  • Using select() is more generally applicable, you could also wait for network traffic.
  • Your hardware will not be serviced as often as possible but with a fixed pause in between. On the upside, you also don't use a full CPU then.
Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55