pavinjoseph
Posts: 10
Joined: Mon Mar 02, 2015 8:43 am

Need advise on realtime programming

Fri Apr 13, 2018 5:22 pm

Hey everyone,

I'm trying to create an automated robotic machine which detects various signals from operator and acts accordingly. It has 4 inputs (3 switches, 1 signal receiver) and 8 outputs (4 LEDs, 1 beeper, 1 gear fwd/bwd, 1 motor power, 1 transmitter ).

I'm using the python RPi.GPIO library to interact with the Raspberry pi 2 model b GPIO pins.

The first problem I ran into was with the `add_event_detect` function not detecting bidirectional change of state inputs correctly even after experimenting with various bouncetime values. Below is the code I ended up using to switch on/off an LED on receiving input from switch.

Not working reliably, slow to respond to changes or not responding to changes:

Code: Select all

# Adding event detect to the switch pin
GPIO.add_event_detect(swtch, GPIO.BOTH, updateledstate, 100)

Working reliably, fast to respond to changes when function is invoked from a while loop every 10ms:

Code: Select all

# This method will be invoked when an event occurs
def updateledstate(channel):
    global led
    global ledstate
    if GPIO.input(channel) == 0:
        # Turn off LED.
        if ledstate == 1:
            ledstate = 0
            GPIO.output(led, ledstate)
            print "LED is OFF"
            
    if GPIO.input(channel) == 1:
        # Turn on LED.
        if ledstate == 0:
            ledstate = 1
            GPIO.output(led, ledstate)
            print "LED is ON"

while(True):
	updateledstate(swtch)
	time.sleep(0.01)
I would appreciate your input on whether the above is a good strategy to receive change of state inputs but I can't help feeling its inefficient somehow although the Pi handles it without breaking any sweat with 4 inputs.

The second and more frustrating problem I've encountered is reacting to these inputs inside the above while loop. I have written idempotent programs that processes information or inputs sequentially and then processes outputs with database transactions to log change of state and other variables. With realtime programming that updates every 10 ms and produces outputs within 5 ms I'm at a loss as to how to react to various inputs without use of any database to refer to previous input states. I tried making do with primitive counters but the more complex the inputs the harder it gets to code and maintain. An example of this is when one of the switches is used to set a timer. Every change of state is detected as a single press of the pushbutton switch (and increments timer by 1s), if the switch is pressed continuously for 10s the timer is reset. Below is the extremely un-intuitive code :!: I wrote inside the while loop to implement this timer switch.

Code: Select all

# inside while loop which is updated every 10 ms
# Blue/Set timer button action.
# pbval is a variable which contains the current input state
# x, total_pbval are counters
# timer_val is the current timer value in seconds

            if pbval == 0:
                # Turn off blue led.
                GPIO.output(led_blue, 0)
                total_pbval = 0
                pbval_a = pbval, x
            
            if pbval == 1:
                # Turn on blue led.
                GPIO.output(led_blue, 1)
                total_pbval += 1
                pbval_b = pbval, x
                
            if total_pbval >= 1000:
                print "BLUE button pressed for 10s. Proceed to reset timer."
                os.system("/bin/echo 0 > /root/timer.file")
                timer_val = 0
                # Flash blue led and beeper for reset confirmation.
                for xi in xrange (3):
                    GPIO.output(led_blue, 1)
                    GPIO.output(beeper, 1)
                    time.sleep(0.5)
                    GPIO.output(led_blue, 0)
                    GPIO.output(beeper, 0)
                    time.sleep(0.5)
            
            if timer_val not in xrange (1, 601):
                if timer_val >= 601:
                    timer_val = 0
                    os.system("/bin/echo 0 > /root/timer.file")
                elif timer_val < 0:
                    timer_val = 0
                    os.system("/bin/echo 0 > /root/timer.file")
                timer_ok = 0
                # Turn off amber led.
                GPIO.output(led_amber, 0)
            else:
                timer_ok = 1
                # Turn on amber led.
                GPIO.output(led_amber, 1)
            

            # Set time.
            pbval_1 = pbval_a[0]
            x_1 = pbval_a[1]
            
            pbval_2 = pbval_b[0]
            x_2 = pbval_b[1]
            
            # Find change of state. 
            if (x in pbval_a) and ( abs(x_1 - x_2) == 1 ):
                pbval_cur = pbval_1
                pbval_last = pbval_2
            elif (x in pbval_b) and ( abs(x_1 - x_2) == 1 ):
                pbval_cur = pbval_2
                pbval_last = pbval_1
            else:
                pbval_cur = 0
                #pbval_last = 0
            
            # Find if change is from 0 to 1: rising edge.
            if pbval_cur == 1 and pbval_last == 0:
                #we have rising edge.
                timer_val += 1
                command = "/bin/echo %d > /root/timer.file" %timer_val
                os.system(command)
                if timer_val not in xrange (1, 601):
                    if timer_val >= 601:
                        timer_val = 0
                        os.system("/bin/echo 0 > /root/timer.file")
                    elif timer_val < 0:
                        timer_val = 0
                        os.system("/bin/echo 0 > /root/timer.file")
                    timer_ok = 0
                    # Turn off amber led.
                    GPIO.output(led_amber, 0)
                else:
                    timer_ok = 1
                    # Turn on amber led.
                    GPIO.output(led_amber, 1)
How can I do the above better?

Thank you!

User avatar
paddyg
Posts: 2190
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Need advise on realtime programming

Sat Apr 14, 2018 9:12 pm

Don't know why the callback is unreliable - have you tried putting print statements in the function to see what's happening. i.e. is the function being called but the input has changed when it does the logic, or is it turning on and off very quickly? It's a rather indirect help but over the years I have found that whenever I have bits of code that seem to be repeated but with the zeros change to ones etc it's worth going back and thinking through what the code's actually doing and changing the way it does it. Inevitably the code works better, has less bugs and is easier to fix. So in your first function your underlying logic is 'set the state to the new state if the new state is different from the current state' something like:

Code: Select all

...
STATE = ['OFF', 'ON'] 
...
def updateledstate(channel):
    global led
    global ledstate
    new_state = GPIO.input(channel)
    if new_state != ledstate:
        ledstate = new_state
        GPIO.output(led, ledstate)
        print( "LED is {}".format(STATE[ledstate]))
There looks to scope for doing that kind of thing in the second chunk of code but I've not really looked at it.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

pavinjoseph
Posts: 10
Joined: Mon Mar 02, 2015 8:43 am

Re: Need advise on realtime programming

Sat Apr 14, 2018 10:43 pm

paddyg wrote:
Sat Apr 14, 2018 9:12 pm
Don't know why the callback is unreliable - have you tried putting print statements in the function to see what's happening. i.e. is the function being called but the input has changed when it does the logic, or is it turning on and off very quickly? It's a rather indirect help but over the years I have found that whenever I have bits of code that seem to be repeated but with the zeros change to ones etc it's worth going back and thinking through what the code's actually doing and changing the way it does it. Inevitably the code works better, has less bugs and is easier to fix. So in your first function your underlying logic is 'set the state to the new state if the new state is different from the current state' something like:

Code: Select all

...
STATE = ['OFF', 'ON'] 
...
def updateledstate(channel):
    global led
    global ledstate
    new_state = GPIO.input(channel)
    if new_state != ledstate:
        ledstate = new_state
        GPIO.output(led, ledstate)
        print( "LED is {}".format(STATE[ledstate]))
There looks to scope for doing that kind of thing in the second chunk of code but I've not really looked at it.
Thanks for that, I went through the code a few more times but ended up adding a lot more comments than change the logic of what was already working. I was hoping realtime programming would be somehow easier to write and maintain in python than in ladder logic but it is not so. Ah, but I enjoy writing python more than any LD program so i guess there's that. If you have good references or books for realtime programming, would really appreciate sharing it.

Idahowalker
Posts: 333
Joined: Wed Jan 03, 2018 5:43 pm

Re: Need advise on realtime programming

Sat Apr 14, 2018 11:10 pm

Have you looked into using interrupts instead of loops and sleep?

I do not use loops and sleeps in a program that runs 24/7, except for upgrades.
Being a programmer: She says go to the store get 1 bottle of milk, if they have eggs bring back 6. She gets mad cause I brought back 6 bottles of milk.

User avatar
paddyg
Posts: 2190
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Need advise on realtime programming

Sun Apr 15, 2018 9:08 am

You would find it much easier to use gpiozero which has lots of the functionality you're trying to replicate built in and there are lots of examples and recipes in the excellent documentation.

Looking back at your code and trying to make it DRY will help you to restructure it and make the logic much easier to follow. Where you have repeated bocks of identical or similar code should they be put into a function or part of an additional loop? Threading is also very important for this kind of application, for instance your three flashes block stops the execution of the rest of your main while loop so no other input or output happens.

So when I look at the first few lines I see that

Code: Select all

            if pbval == 0:
                GPIO.output(led_blue, 0)
...
            if pbval == 1:
                GPIO.output(led_blue, 1)
...
so assuming that pbval is either 0 or 1 this is the same as

Code: Select all

            GPIO.output(led_blue, pbval)
i.e. ouput to the GPIO every loop unconditionally whereas this logically only needs to happen if there is a change in pbval. So straight away I would put at the end of the main loop set pbval_last = pbval and only do the on/off logic if pbval != pbval_last

It's hard to figure out your code without knowing where x comes from but using the condition if .. in range(..) seems odd, and unnecessary. Surely you could use simple inequalities (which you then do)

Code: Select all

            if timer_val < 0 or timer_val > 600:
                timer_val = 0
                os.system("/bin/echo 0 > /root/timer.file") # why not use python's f.write('0')
                timer_ok = 0
            else:
                timer_ok = 1
            GPIO.output(led_amber, timer_ok)
            # you're setting this every loop, I would only switch on change which
            # might need another last-state variable.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

pavinjoseph
Posts: 10
Joined: Mon Mar 02, 2015 8:43 am

Re: Need advise on realtime programming

Sun Apr 15, 2018 9:57 pm

Idahowalker wrote:
Sat Apr 14, 2018 11:10 pm
Have you looked into using interrupts instead of loops and sleep?

I do not use loops and sleeps in a program that runs 24/7, except for upgrades.
I started by testing RPi.GPIO's `add_event_detect` interrupt which runs a callback on detecting bidirectional change of state. The callback was being run alright but it just wouldn't detect some change of state, I'd say its about 60% accurate with the right bouncetime value and with the rest it misses an edge. Loops and sleeps proved to be much more reliable and maybe even more high performance. I wanted 10-20 ms response times like a PLC and the Pi 2 seems to be handling it without any issues (~ 2% CPU usage while polling 4 inputs every 10ms).
You would find it much easier to use gpiozero which has lots of the functionality you're trying to replicate built in and there are lots of examples and recipes in the excellent documentation.

Looking back at your code and trying to make it DRY will help you to restructure it and make the logic much easier to follow. Where you have repeated bocks of identical or similar code should they be put into a function or part of an additional loop? Threading is also very important for this kind of application, for instance your three flashes block stops the execution of the rest of your main while loop so no other input or output happens.
The documentation for gpiozero looks good, very easy to use functions. I'll have to give it a try, hope the interrupts won't be like RPi.GPIO's! I'd take reliability and performance over efficiency with this program. Whether or not gpiozero works out as well as its docs I'd still rewrite the program with your very helpful recommendations: cutting down repeated blocks of code, performing output changes based on current state, performance improvements like removing xrange and os.system calls, etc.

Regarding the threading recommendation, during the initial stages I did look into making the program use python's excellent multiprocessing library but this program is meant to be blocking in certain places like when the beeper/led is flashing. One of the actions that follows a long beep pattern is to turn on an industrial xray tube (which is also main thread blocking until it turns off) so that the operator can move safe distance away.

User avatar
paddyg
Posts: 2190
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Need advise on realtime programming

Mon Apr 16, 2018 9:55 am

I've had problems with callback functions on gpio state change before, and I think there are some quirks in the GPIO code, but they've generally been because of stray edges i.e. as a rotary encoder 'made' or 'broke' it produced lots of spikes, the bouncetime never seemed to filter them properly. The solution was to keep track of the state the switch was in and the states it could move to and to re-read the inputs within the callback function, a bit messy but worth it for the benefit of splitting the code in a logical way.

Threading module is probably fine here (syntax is intentionally similar to multiprocessing and accessing variables is simpler). Rather than having everything in a loop with sleeps to synchronize things I would keep track using time.time() - *especially* if health and safety issues are involved (remember Toyota and spagetti code?) i.e. I would set trigger times using delays, along the lines of

Code: Select all

SAFETY_DELAY = 3.0
...
while True:
    tm=time.time()
...
    if button_down and (tm - last_button_change_tm) > 10.0:
        x-ray_start = tm + SAFETY_DELAY
...
    if tm > x-ray_start:
...
   time.sleep(0.005)
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

robbes
Posts: 135
Joined: Sun Jan 20, 2013 7:11 pm
Location: Canada - off the west coast

Re: Need advise on realtime programming

Tue Apr 17, 2018 2:03 am

On the programming logic side of things, have you looked into using a formal finite state machine model - for example the module called "transitions" at https://github.com/pytransitions/transitions ? It might save you a bit of grief in keeping track of the states.

pavinjoseph
Posts: 10
Joined: Mon Mar 02, 2015 8:43 am

Re: Need advise on realtime programming

Wed Jun 06, 2018 3:50 pm

robbes wrote:
Tue Apr 17, 2018 2:03 am
On the programming logic side of things, have you looked into using a formal finite state machine model - for example the module called "transitions" at https://github.com/pytransitions/transitions ? It might save you a bit of grief in keeping track of the states.
This should help me clean up the code quite a bit. Thanks!

Return to “Python”

Who is online

Users browsing this forum: przemof and 12 guests