Interrupt for-loop on Raspberry Pi

Hi,

If just started using Anvil in combination with a Raspberry Pi and a whole new world of possibilities has opened! :smiley:
I’m not a hardcore programmer, so perhaps the question I’m about to ask is a really easy one to answer.

So with Anvil I want to control the speed, direction and status of a stepper motor which is connected to the Raspberry Pi.
After reading to the documentation and seeing a couple of tutorials, I got it working! There is only one big bug remaining: I cannot stop the motor while it is running.

In Anvil, I control the on/off of the motor using a checkbox (AAN/UIT on photo)

In the Raspberry Pi, i make a pulsed output using a for-loop: after 1000 pulses, the motor will stop.
2020-12-16 21_18_00-Raspberry PI (raspberrypi) - VNC Viewer
I want to make a interrupt (break) on the end of the for-loop, based on the status ‘cb_status_motor’ which I requested earlier.

Is it possible to call back for the variable in Anvil, so to do a request to the Anvil server from the Raspberry Pi? Are is it breaking the server-client rule?

Hope this is clear! :slight_smile:

Kind regards,
Jan

Hi @janfransen_kenis,

This link may help:
https://www.tutorialspoint.com/python/python_loop_control.htm

Hi Tony,
Thank you for the response. A accidentally pressed on the ‘Reply’ button before I was finished, so my question was not finished yet and I could not edit it, because it was my first post… I editted the question above.
Indeed, a ‘break’ would solve my problem. The only problem is that I want to use a condition from Anvil, so I want to do a request to the Anvil server. Is this possible?

If you put your if statement where your arrow is, eg :

if <condition> == "QUICK-STOP!":
    break

the break will stop the inner most loop, in this case the only loop.

You can call server functions from other server functions, either directly if they are in the same server module (or included), or using anvil.server.call if they are decorated as such.

Does that help?

Hi David,

Thank you for your quick response!
I’ve implemented this in the Raspberry Pi:

Like you can see, he will not reach the anvil.server.call. I get this message in Anvil:

This is my server code:
image

I guess there is something wrong with my status in the server code.
Do you perhaps know the solution?
Thank you!

Here’s a better way to do that.

Replace -

anvil.server.call("get_motor_status", status)

with -

statusRasp =  anvil.server.call("get_motor_status")

and you can remove status as a required parameter in the function definition as it’s not really necessary.

The issue is you are passing an undefined variable to the function (status). Also, I’ve not tested it but I’m not sure if variables are passed by reference between the client and the server (which is what your code would rely on as is).

It would be much better to fix bug if a clone link is provided. You can search in forum to get a clone link of your app

Hi David,

Bad luck: still got the same error message :pensive:

I’ll share the clone link below. Thanks for the tips!

Hi Tony,

Clone link: https://anvil.works/build#clone:VSGEZTAQUOND5YB5=IOL5IX3QLBD7G6UFTUTSGHOH
Hope this helps!

I’m getting slightly confused here …

The clone link doesn’t contain the code you originally screenshot, ie I cannot see anything calling get_motor_status().

In your original post you had “status” as a parameter to a function call but it had not previously been defined. That was the cause of the error for sure. My suggestion would do away with the need to pass “status”.

You say it didn’t work and shows the same error - please can you paste the whole form code where the error occurs, so I can see what surrounds it and how you modded it?

Also, can you please paste rather than screenshot any code you post? Makes it easier for me to read and copy if I need to test something.

Hi David,

I understand your confussion: there are 2 application talking to eachother:
ANVIL --> Raspberry Pi (Microcontroller). With the Raspberry Pi, I’ll control a stepper motor!

This is the code from the Raspberry Pi:

#Running a NEMA17 stepper motor with a TB6600 driver

import RPi.GPIO as GPIO
import time
import anvil.server
#from anvil.tables import app_tables

GPIO.setmode(GPIO.BOARD) #read the pin as board instead of BCM pin

anvil.server.connect("XXXXX")

DIR = 33 # Direction of the stepper motor
PUL = 35 # Pulses send to the stepper motor
ENA = 36 # ON/OFF stepper motor


GPIO.setwarnings(False)
GPIO.setup(DIR, GPIO.OUT)
GPIO.setup(PUL, GPIO.OUT)
GPIO.setup(ENA, GPIO.OUT)

steps = 1000
statusRasp = False

GPIO.output(ENA, True) 
    
@anvil.server.callable
def motor_aan_cb(direction,speed,motor_aan_cb):
    direction_Rasp = direction
    speed_Rasp = speed
    cb_status_motor = motor_aan_cb
    
    print("Speed of motor:",speed)
    print("Direction of motor: ",direction)
    print("Status of motor: ",cb_status_motor)
    
    if cb_status_motor == True:
        for x in range(steps):
            GPIO.output(ENA, False) 
            GPIO.output(DIR, direction_Rasp)
            GPIO.output(PUL, 1)
            time.sleep(speed_Rasp)
            GPIO.output(PUL, 0)
            time.sleep(speed_Rasp)
            statusRasp = anvil.server.call('get_motor_status',status)
            print("StatusRasp:",statusRasp)
            if statusRasp == False:
                break
    else:
        print('Wait on cb_status_motor to become 1')
        GPIO.output(ENA, True)

anvil.server.wait_forever()

You are still passing “status” to the function :slight_smile:

statusRasp = anvil.server.call('get_motor_status',status)

Remove that and at least that error will disappear.

Sorry, I didn’t see that. I removed the ‘status’.
If I try running the code, the following error appears:

I guess you cannot refer to self.motor_aan_cb.checked in the servermodule? I found it strange, because normally if you write something in the server, it would make a suggestion after the dot.In this case, this wasn’t happening.

Hi @janfransen_kenis,

Your root issue is not the code itself, but how anvil actually works. I suggest you complete these tutorials to have better understanding. It might take a bit of your time, but you will surely get the idea how things work.



1 Like

It looks to me like there are at least 3 different computers here, each running its own part of the program:

  1. The Client (browser)
  2. The Server
  3. The Raspberry pi

Each has its own distinct processing unit(s) and memory, and its own copy of Python. These do not share memory, so they cannot share any variables (e.g., self). Instead, when one machine calls a function on another machine, information is copied (transmitted by wire) from one machine to the next.

When you call from one computer to another, parameters are copied from the calling computer to the called computer. Likewise, return values are copied from the called computer to the calling computer.

This explains why self, in the Server module, is unknown to the Server. In this case, self refers to an object (a checkbox) that exists only in the Client machine (the browser).

4 Likes

This is indeed exactly my setup! :+1: Thanks for the clarification!
In the Client (browser), i will copy 3 values to the Raspberry pi:

In the Raspberry Pi, the following function will we executed, because this is called by the Client:
image

The for-loop will start when the checkbox is checked in the Client.
image

Let’s say this for-loop last for 20 min. After 10 min, you encounter a problem and you want to stop the motor. So my first reaction will be: uncheck the checkbox in the Client:

image

So at the end of the for-loop,I want to call for the status of this checkbox:
if the checkbox is checked, continue with the for-loop, if not checked: break.

image

So I have to make a call from the Raspberry Pi --> the Client! And this is the problem I think. Is it possible to call the Client and ask for the status of the checkbox ‘Motor AAN’ at that moment?

If not, I was trying this:
I thought this might work: Raspberry Pi --> Server (Anvil) --> Client (Webbrowser)

I saw this in one of your comments on the forum: Calling user input in the server
For testing, I made a new checkbox in my Client:
image
If this is pressed,this will happen:

image
And this will be executed on the Anvil server:
image

In the Raspberry Pi, I will implement this code:
image

Afterwards, I get this message:


I guess it’s because this is not yet executed in the Client:
image

But I’m not really sure if it’s possible to make a call from the Raspberry Pi to the Client. That’s why I wanted to ask first if this a good path or I should change it up and install a physicall button on my Raspberry Pi that acts as ‘an emergency button’. :thinking: It is not ideal, because in this way, you cannot turn off your motor using a web application, which is really cool.

In the end, I just want to start and stop the motor with the website.

Hope this makes things a bit clearer. Sorry for the miscommunication. It’s my first time using a server-client, so it’s all a bit new to me. And it makes it all more complicated, if you add a third player (Raspberry Pi)!

Sorry, no. In the Anvil world, nobody can call the Client. (High-level design facts like this are not well-documented, so this fact would be very easy to miss.) But the Client can call the Pi (or any other Uplink program), to notify it that the checkbox has changed. Something like this, perhaps, in your Pi’s program:

motor_disabled = False

@anvil.server.callable
def set_motor_disabled_status(new_status):
    global motor_disabled 
    motor_disabled = new_status

@anvil.server.callable
def motor_aan_cb(direction, speed, motor_aan):
    global motor_disabled
    ...  # your code here
            time.sleep(speed_Rasp)
            if motor_disabled:
                break

The way your code is organized, it depends on the Pi being able to call the Client. Anvil doesn’t allow that. So I see two choices:

A. Have the Pi call the Server, instead, for that information. But, in order to have that information:

  1. The Client must call the Server with that information, whenever it changes.
  2. The Server-side program (your Server modules) closes when it returns to the caller, so its variables (and their values) disappear at that time. Before then, the Server must preserve that information in some longer-lasting medium (i.e., in a database table or session cache).
  3. On demand from the Pi, the Server code must go look it up, and return it.

That is going to be fairly complicated. But there is a much simpler way.

B. Eliminate the middleman: have the Client call the Pi directly, to inform it of any important change. A strategy like this, perhaps:

  1. Add a server.callable function on the Pi.
  2. The Client uses it to notify the Pi when an important variable has changed.
  3. The Pi uses it to update a corresponding global variable, thus preserving the updated value, in a place where other functions can see it. This can occur at any time (think of it as like a hardware interrupt, but with Python threads).
  4. Other functions, running on the Pi can later read that variable, and act accordingly.

A server.callable function should return in a few seconds. (I think the timeout limit is 30 seconds.) Otherwise, the call will “time out”, which the caller sees as an error.

1 Like

Hi p.colbert,

Sorry for the delay. Now it’s Sunday, so I have some time to do some tinkering :ok_hand:
I try to implement the changes you descripted and a re-wrote some of the code. Thank you for the suggestions!

Now, I’m using just buttons instead of check boxes. Just sending pulses makes more sense! :blush:


New code Anvil: https://anvil.works/build#clone:VSGEZTAQUOND5YB5=IOL5IX3QLBD7G6UFTUTSGHOH

New code Raspberry Pi:

> #Running a NEMA17 stepper motor with a TB6600 driver
> 
> import RPi.GPIO as GPIO
> import time
> import anvil.server
> from anvil.tables import app_tables
> 
> GPIO.setmode(GPIO.BOARD) #read the pin as board instead of BCM pin
> 
> anvil.server.connect("2FRNNXWWCUKIWTQKJUY27C3X-VSGEZTAQUOND5YB5")
> 
> DIR = 33 # Direction of the stepper motor
> PUL = 35 # Pulses send to the stepper motor
> ENA = 36 # ON/OFF stepper motor
> 
> 
> GPIO.setwarnings(False)
> GPIO.setup(DIR, GPIO.OUT)
> GPIO.setup(PUL, GPIO.OUT)
> GPIO.setup(ENA, GPIO.OUT)
> 
> steps = 1000
> 
> motor_aan_Rasp = False
> motor_uit_Rasp = False
> 
> GPIO.output(ENA, True)
> 
> @anvil.server.callable
> def motor_uit():
>     global motor_aan_Rasp
>     motor_aan_Rasp = False
>     global motor_uit_Rasp
>     motor_uit_Rasp = True
>     print('Motor_uit_Rasp in motor_uit(): ',motor_uit_Rasp)
>     motor_start()
> 
> @anvil.server.callable
> def motor_aan(direction,speed):
>     global direction_Rasp
>     direction_Rasp = direction
>     global speed_Rasp
>     speed_Rasp = speed
>     
>     global motor_aan_Rasp
>     motor_aan_Rasp = True
>     global motor_uit_Rasp
>     motor_uit_Rasp = False
>     motor_start()
> 
> def motor_start():
>     global motor_aan_Rasp
>     print('Motor_aan_Rasp in motor_start():',motor_aan_Rasp)
>     if (motor_aan_Rasp == True):
>         for x in range (steps):
>             global motor_uit_Rasp
>             GPIO.output(ENA, False) 
>             GPIO.output(DIR, direction_Rasp)
>             if motor_uit_Rasp == True:
>                 break
>             GPIO.output(PUL, 1)
>             time.sleep(speed_Rasp)
>             GPIO.output(PUL, 0)
>             time.sleep(speed_Rasp)
>     else:
>         print('Wait on MOTOR AAN')
>         GPIO.output(ENA, True)
> 
> anvil.server.wait_forever()

The global variable ‘motor_uit_Rasp’ is updating fine! But the problem is, it will update after it has excecuted the for-loop (see gif below). As long as the for-loop continues, Anvil will give the wait symbol. It looks like it’s not just sending the pulse, but waiting until the for-loop has completed and afterwards sends the motor_uit() command.

Looks like you’re really close! I suspect that the Client is waiting for the return (at the end of the function that contains the for loop), before it does anything else.

If so, then the Uplink program would need to be more independent. The @anvil.server.callable functions should just receive data, not do anything else directly. To trigger longer-running work, a separate loop in the Uplink program could watch those variables, and run your for loop when appropriate.

Correct - anvil.server.call is blocking in python client code i.e. it will wait for the return value of the server function

I don’t want to lead you down a rabbit hole but you might also want to consider using threading

Some pseudo code

import threading

stop = False

def Motor(threading.Thread):
  def run(self):
    # run the motor
    for i in range(1000):
        if stop:
           return

@anvil.server.call
def start_motor():
  motor = Motor()
  motor.start() # I am not blocking
  return

@anvil.server.call
def stop_motor():
    global stop
    stop = True

anvil.server.wait_forever()
1 Like