Python, Webiopi and Raspberry Pi

2.3k views Asked by At

I am trying to control my Raspberry Pi car via wireless and webiopi. The basic function works fine - have the interface where I click fwd and the car will go forward and when I release the button it will stop.

I now want to integrate the Ultrasonic distance sensor so that when I drive forward, the car should stop when something is in front of it. I have it going where when I click to fwd button, the car will drive and stop when something is in range but it will only stop when something is in range and not when I release the button. My while loop is somehow looping (stuck) and not reading the release button function from webiopi.

Can somebody please help - been at it for days now and not sure where I am going wrong :-(

Here is the loop from my python script:

def go_forward(arg):
  global motor_stop, motor_forward, motor_backward, get_range
  print "Testing"
  print mousefwd()
  while (arg) == "fwd":
      print (arg)
      direction = (arg)
      dist = get_range()
      print "Distance %.1f " % get_range()
      if direction == "fwd" and get_range() < 30:
          motor_stop()
          return
      else:
          motor_forward()

And here is the code from my webiopi function call:

 function go_forward() {
              var args = "fwd"
              webiopi().callMacro("go_forward", args);
      }
 function stop1() {
              var args = "stop"
              webiopi().callMacro("go_forward", args);
              webiopi().callMacro("stop");
    }

This is how I have it now but still not working (I am a total noob :-) ) :

 def go_forward(arg):
  global motor_stop, motor_forward, motor_backward, get_range
  print "Testing"
  direction = arg
  while direction == "fwd":
      print arg
      dist = get_range()
      print "Distance %.1f " % get_range()
      if get_range() < 30:
         motor_stop()
      elif direction == "fwd":
         motor_forward()
      else:
         motor_stop()

Maybe a slight step forward. See that webipi uses its own 'loop' and I added the loop code to check what the status of the motor GPIO's are and the distance and if the motor is running and if the distance is too short then stop. Car moves now when I press the forward button and stops when I release it and when moving forward and distance is less then 30cm it will stop. Only problem is that when the distance is too short and I press the forward button too quickly multiple times, I now get a "GPIO.output(Echo,1) _webiopi.GPIO.InvalidDirectionException: The GPIO channel is not an OUTPUT" error :-( .

The Code looks now like this:

 def go_forward(direction):
   motor_forward()

 def loop():
   if get_range() < 30 and GPIO.digitalRead(M1) == GPIO.HIGH:
     stop()
     print "stop"
   sleep(1)
2

There are 2 answers

6
rmunn On BEST ANSWER

I'll answer your main problem in a minute, but first I want to mention something you're doing as a Python beginner, which is making your life more complicated than it needs to be.

There's no need for the global statement you have at the start of your function. In Python, you only need to use the global statement if you want to write to a global variable. If all you need to do is read the variable, or call the function, then there's no need for the global statement. Any names used in your function, whether they're names of variables or names of functions, will be searched for in the following order: local, enclosing, global, builtin. "Local" means names defined inside your function, like your direction variable. "Enclosing" is a rule used when you define a function inside another function. This is useful in many situations, but is a bit of an advanced topic and you don't need to worry about it for now. "Global" means names (of variables, or functions, or classes) defined at the top level of your program -- such as your motor_stop function among others. And finally, "builtin" means Python's built-in names, like str or int or file.

So if you left out the global statement, then Python would search for the name motor_stop (and the other function names) as follows:

  1. Local: Is there a motor_stop defined in this function? No. Moving on.
  2. Enclosing: Is this function defined inside another function? No, so the "enclosing" rule doesn't apply. Moving on.
  3. Global: Is there a motor_stop defined at the top level of the program? Yes. Found it.
  4. (Builtin): This rule is never checked at all, because the name was found by the "global" rule.

All of your functions are defined at the top level of your program, so the "global" rule will find them with no need for the global statement. This should make your life a little simpler.

Now for your main question: why your code is looping forever. That's because the test condition your while loop will never become false. You are looping on while direction == "fwd", and the code inside the while loop never changes the direction variable. So every time it reaches the end of the loop, it goes back to test the condition again. The direction variable still contains the string "fwd", and so the while loop runs a second time. Then the direction variable still contains the string "fwd", and so it runs a third time. And so on, and so on.

One way to exit the while loop when you want it to exit would be to set the direction variable to something else, like "stop", when you want to stop. (E.g., after you call motor_stop()). Another way would be to use the return statement to exit the function at that point. But my suggestion would be to use the break statement instead, which means "At this point, immediately exit the loop I'm in." It works on both while loops and for loops, and is a very useful statement to know about. It looks like you want your while loop to exit any time you call motor_stop(), so there are two places you'd put a break statement:

def go_forward(arg):
  print "Testing"
  direction = arg
  while direction == "fwd":
      print arg
      dist = get_range()
      print "Distance %.1f " % get_range()
      if get_range() < 30:
         motor_stop()
         break
      elif direction == "fwd":
         motor_forward()
         # No break statement here, so the while loop continues
      else:
         motor_stop()
         break

There are a few other changes I'd suggest making. First, you call get_range() three times in your loop, but you really only need to call it once. You've already assigned its result to a dist variable, so why not use that variable?

def go_forward(arg):
  print "Testing"
  direction = arg
  while direction == "fwd":
      print arg
      dist = get_range()
      print "Distance %.1f " % dist
      if dist < 30:
         motor_stop()
         break
      elif direction == "fwd":
         motor_forward()
         # No break statement here, so the while loop continues
      else:
         motor_stop()
         break

And second, there's no way inside the while loop for direction not to be "fwd", so the final else is unnecessary, and your function can become:

def go_forward(arg):
  print "Testing"
  direction = arg
  while direction == "fwd":
      print arg
      dist = get_range()
      print "Distance %.1f " % dist
      if dist < 30:
         motor_stop()
         break
      else:
         motor_forward()

Finally, you have a function parameter named arg, which is a name that tells you nothing about what it's used for. You immediately assign it to a variable called direction, which is a much better name. So why not use that as the parameter name in the first place? There's no rule that says a function's parameters must be called "arg"; they can be called anything you like (except one of Python's reserved words like break or if). So let's give your function a more meaningful parameter name, and that will simplify it even further:

def go_forward(direction):
  print "Testing"
  while direction == "fwd":
      print direction
      dist = get_range()
      print "Distance %.1f " % dist
      if dist < 30:
         motor_stop()
         break
      else:
         motor_forward()

There. That's much easier to read and understand, and it should solve the problem you were seeing of your while loop not exiting when the car gets too close to an object.

(There's still one possible problem I can see, which is that the while loop might not exit while the car is driving forward, even though you released the "forward" button -- but I'd have to see your motor_forward() code to know how to solve that one. It might be as simple as putting a break statement after motor_forward(), in which case there would really be no need for a while loop at all -- but I'd have to know how your motor_forward() code interacts with webiopi and how webiopi's event-handling code works. None of which I know right now, so I'm going to answer what problems I can answer now even if my answer is incomplete, rather than leave you with no answer at all.)

Edit by Mark: Here is the motor_forward code:

 def motor_forward():
     GPIO.output(M1, GPIO.HIGH)
     GPIO.output(M2, GPIO.LOW)
     string = "echo 0=130 > /dev/servoblaster"
     os.system(string)
2
gkusner On

Try this - you always stop on range but may want to stop on direction

  if get_range() < 30:
      motor_stop()
  elif direction == "fwd":
      motor_forward()
  else:
      motor_stop()

btw you dont need the (arg) only arg when you reference it

print arg

the parens in the call signature are there to show its a parm to a function