papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Robot web control and distance sensor working in parallel

Mon Jan 22, 2018 7:25 am

I built a robot that is controlled from a web app (using flask). The robot has a distance sensor. The object of the exercise is: while the robot is “driven", if an object is detected, it should stop. Basically it should listen to the web form all the time so it can detect the keypress and also check the distance and stop if it comes close to an object
“While” loops do not seem to help. I’ve been thinking of threading but I have not grasped the idea yet.
Here is my simple code (complete code in my Gdrive, https://goo.gl/GLk7FA)
Any help, pointing to the right direction, pseudocode, code snippets will be greatly appreciated

Code: Select all

# Orig Author: James Poole
from flask import Flask,render_template, url_for, request, redirect, session
from time import sleep
import motor
import distance
tdist=[]
app=Flask(__name__)
app.secret_key = 'You Will Never Guess'
@app.route('/')
def index():
    return render_template('index.html')
@app.route('/drive', methods=['POST'])
def drive():
    dist2obj=distance.distance_detected(tdist=[])
    while  dist2obj>15:
        theway=request.form.get('moveit')
        dist2obj=distance.distance_detected(tdist=[])
        
        if theway=="F":
            goforth()
        #endif theway=='F'
                 
        if theway=="B":            
            goback()
        #endif theway=='B'
                 
        if theway=="L":  
            goleft()
            
        if theway=="R":
            goright()
        
        if theway=="S": 
            dostop()
            
    else:
        dostop()
    return  render_template('index.html' ,  speed=session['dc'])
def goforth():
            print('going forw')
            dc=session['dc']
            #print(dc)
            dc=dc+20
            if dc>100:
                dc=100
            sleep(1)
            motor.Motor_dc(dc,dc,0,0)
            session['dc']=dc
            sleep(1)
def goback():
        if session['whichway']=='F':
            dc=session['dc']
            if session['dc']>0: #break
                dc=dc-20
                motor.Motor_dc(0,0,dc,dc)
                session['dc']=dc
                session['whichway']="F"
            else:
                session['whichway']="B"        
        else:
             dc=session['dc']
             dc=dc+20
             if dc>100:
                 dc=100
             motor.Motor_dc(0,0,dc,dc)
             session['dc']=dc
             session['whichway']="B"
def goleft():
        dc=session['dc']
        if  session['whichway']=='S' or dc==0  :
            dc=30
            motor.Motor_dc(0,dc,dc,0)
            sleep(0.3)
            motor.Motor_dc(0,0,0,0)
        elif session['whichway']=='F':
            motor.Motor_dc(0,dc,dc,0)
            sleep(0.3)
            motor.Motor_dc(dc,dc,0,0)
        elif session['whichway']=='B':
            motor.Motor_dc(0,dc,dc,0)
            sleep(0.3)
            motor.Motor_dc(0,0,dc,dc)
def goright():
        dc=session['dc']
        if  session['whichway']=='S' or dc==0  :
            dc=30
            motor.Motor_dc(dc,0,0,dc)
            sleep(0.3)
            motor.Motor_dc(0,0,0,0)
        elif session['whichway']=='F':
            motor.Motor_dc(dc,0,0,dc)
            sleep(0.3)
            motors.Forwards(dc,dc,0,0)
        elif session['whichway']=='B':
            motor.Motor_dc(dc,0,0,dc)
            sleep(0.3)
            motors.Backwards(0,0,dc,dc)
        else:
            motor.Motor_dc(dc,0,0,dc)
            sleep(0.3)
            
def dostop():
        theway="S"
        dc=0
        session['dc']=0
        session['whichway']='S'
        motor.Motor_dc(0,0,0,0)

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

Re: Robot web control and distance sensor working in parallel

Mon Jan 22, 2018 10:42 am

As the Flask functions only run when requested by the web request you are probably best off moving the contents of your drive function to a different thread with a loop forever in it and make your Flask get() functionality in drive() simply set various state values to effect the behaviour.

In something I did recently I wanted to send info to and from GPIO and the Flask server and I ended up having to have an sqlite database in memory to cope with the situation, hopefully you won't have to do that here if you can get session or global values to work. (But there are issues maintaining info across different threads or processes which Flask, as a web server, has to be rigorous about.)

PS the way you control your motors using sleep functions means that each action 'takes control' and can't be interrupted. A better way is to refactor the whole thing using a 'next action time' concept a bit like

Code: Select all

while True:
  if action == 'F': # this should also be a for key in ['F', 'L'... as below but easier to understand like this
    on_function('F')
    next_off['F'] = time.time() + 1.0
  ...
  for key in ['B', 'L',...
    if next_off[key] > time.time():
      off_function(key)
  ...
  time.sleep(0.05) # just to stop it running flat out
To make this process easier for yourself you should also try to get rid of all the duplication in your functions - a bit like I have implied above. You will find that this approach simplifies your code and makes it easier to debug.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Mon Jan 22, 2018 12:20 pm

Thank you paddyg for the very quick reply and your input.
I will try to effect the changes suggested and report back
Frank

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Tue Jan 23, 2018 8:26 am

I tried to conform to paddyg 's suggestions, so here is a very cut down code

Code: Select all


from flask import Flask,render_template, url_for, request, redirect, session
from time import sleep
import motor
import distance

tdist=[]

app=Flask(__name__)
app.secret_key = 'You Will Never Guess'


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/drive', methods=['POST'])
def drive():
    theway = request.form.get('moveit')
    if theway=='F' or theway=='B' or theway=='L' or theway=='R' or theway=='S':
        moveit('theway')
        session.pop('theway', None)
    return  render_template('index.html')

def moveit(theway):
    while True:
        if distance.distance_detected(tdist=[])<20:
            theway='S'
#        print(theway, distance.distance_detected(tdist=[]) )
        if theway=='F':
            motor.Motor_dc(60,60,0,0)
        
        if theway=='B':
            motor.Motor_dc(0,0,60,60)
        
        if theway=='S':
            motor.Motor_dc(0,0,0,0)

But this does not work. Once the form is clicked, it can not be clicked again and the engines will stop only if the distance sensor is activated or ctl+C is pressed.
Any other ideas or code suggestions?

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

Re: Robot web control and distance sensor working in parallel

Tue Jan 23, 2018 9:04 am

I didn't explain that very clearly, the loop forever has to go inside a threaded function so it runs all the time INDEPENDENT of the Flask triggered functions. I will post an example when I get back tonight.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

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

Re: Robot web control and distance sensor working in parallel

Tue Jan 23, 2018 10:11 pm

Something like the following. I've not sorted out your motor switching functionality, which needs to be thought through, possibly looking in the imported functions. Ideally you would pull out all the variables into dictionary and list variables.

Code: Select all

@app.route('/')
def index():
    return render_template('index.html')
@app.route('/drive', methods=['POST'])
def drive():
    session['lastway'] = session['whichway']
    session['whichway'] = request.from.get('moveit')
    return  render_template('index.html' ,  speed=session['dc'])

def loop_func():
  global session
  while True:
    dist2obj = distance.distance_detected(tdist=[])
    if dist2obj <= 15:
      session['lastway'] = session['whichway']
      session['whichway'] = 'S'
    if session['whichway'] == 'F':
      ... some version of the motor control code in here
    time.sleep(0.01)

t = threading.Thread(target=loop_func)
t.daemon = True
t.start()
If the global, session, system doesn't work then you might have to use sqlite.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Wed Jan 24, 2018 10:03 pm

thank you so much for the help understanding how the code should be written
Unfortunately this way of doing things (using session) as well as using Flask.g for storing values does not seem to work.
The error that I get on both occasions is
RuntimeError: Working outside of request context
This typically means that you attempted to use functionality that needed
to interface with the current application object in a way. To solve
this set up an application context with app.app_context(). See the
documentation for more information.
It seems impossible to pass variable values between functions and this very simple action is rather not well thought out.
On using sql: store the form returned value to a database and retrieve it later for the thread is what you are thinking? Or maybe you have another idea?

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

Re: Robot web control and distance sensor working in parallel

Wed Jan 24, 2018 10:59 pm

This is the kind of thing I had to do with the recent code I mentioned. You (obviously) shouldn't do this slack security style writing (using format()) to insert values into a real online multiuser sql database https://xkcd.com/327/

Code: Select all

import sqlite3
...
conn = sqlite3.connect(':memory:', check_same_thread=False)
c = conn.cursor()
c.execute("CREATE TABLE vals (lastway TEXT, whichway TEXT, dc INTEGER)")
c.execute("INSERT INTO vals VALUES ('S', 'S', 0)")
...
@app.route('/drive', methods=['POST'])
def drive():
    c.execute('SELECT lastway, whichway, dc FROM vals')
    (lastway, whichway, dc) = c.fetchone() # can read a tuple into local names for convenience
    lastway = whichway
    whichway = request.from.get('moveit')
    c.execute("UPDATE vals SET lastway='{}', whichway='{}'".format(lastway, whichway))
    return  render_template('index.html' ,  speed=dc)

def loop_func():
  global session
  while True:
    c.execute('SELECT lastway, whichway, dc FROM vals') # read at start
    (lastway, whichway, dc) = c.fetchone()
    dist2obj = distance.distance_detected(tdist=[])
    if dist2obj <= 15:
      lastway = whichway
      whichway = 'S'
    if whichway == 'F':
    ...
    c.execute("UPDATE vals SET lastway='{}', dc={}".format(lastway, dc)) # write back at end
    time.sleep(0.01)
PS my first solution was to add attributes to the app instance; though this worked I was persuaded on stackoverflow that this was a fundamentally bad idea.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Thu Jan 25, 2018 7:42 pm

Thank you paddyg. Good comic.
I'll try to go down that road and let you know.
Just out of curiosity, why use a DB like sqlite and not use a text file to store a couple of values?

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

Re: Robot web control and distance sensor working in parallel

Thu Jan 25, 2018 9:36 pm

You could write to a file but a) an in-memory database is faster b) writing and reading from 'disk' at high frequency is a bad idea on the RPi where the disk is an SD card c) sqlite does all the work for you. It would be possible to set something up yourself with a 'ram disk', multiprocessing or equivalent but sqlite isn't too tricky to do.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Fri Jan 26, 2018 5:37 pm

Well, I did try using a file read/write method and I am happy to report that it does work.
I will though take your advice and use an sqlite in memory DB which I used before.
Your help and advice has been greatly appreciated. Especially the threading solution
Thank you

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

Re: Robot web control and distance sensor working in parallel

Fri Jan 26, 2018 6:31 pm

Well done getting it to work. It might be simpler, given that you have a running system using file reads and writes, to bolt on mmap functionality see https://docs.python.org/3.5/library/mmap.html but I've not used that and don't know how or if it works in this circumstance (but it sounds like it should).

PS I'm going to have a mess around with mmap now for my own enlightenment!
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Sun Jan 28, 2018 11:57 am

thanks for the tip. I'll a go at it myself

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Wed Jan 31, 2018 9:42 pm

Well, here I am again
The code below was working fine until yesterday

Code: Select all

from flask import Flask,render_template, url_for, request, redirect, session
import os.path
from time import sleep
import motor
import distance
import threading
from myfunc import read_write_file

read_write_file('w+','SS')
tdist=[]
dc=20

app=Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/drive', methods=['POST'])
def drive():
    ways=read_write_file('r',0)
    #print(ways)
    oldway=ways[1:2]
    newway=request.form.get('moveit')    
    read_write_file('w+',oldway+newway)
    return  render_template('index.html')

def loop_func():
    while True:
        thisway= read_write_file('r',0)[1:2]
        oldway = read_write_file('r',0)[0:1]
        print('>>',oldway+thisway)
        dist2obj = distance.distance_detected(tdist=[])
        if dist2obj < 25:
            read_write_file('w+','SS')
            print(dist2obj)
            thisway = 'S'
        if thisway == 'F':
            if oldway=='F':
                dc=dc+10
                if dc>100:
                    dc=100
            motor.Motor_dc(dc,dc,0,0)
        if thisway ==  'B':
            motor.Motor_dc(0,0,dc,dc)
        if thisway == 'S':
            motor.Motor_dc(0,0,0,0)
        if thisway == 'L':
            motor.Motor_dc(0,dc,dc,0)
        if thisway == 'R':
            motor.Motor_dc(dc,0,0,dc)
        sleep(0.1)
        #print(thisway)


t = threading.Thread(target=loop_func())
t.daemon = True
t.start()
    
    
if __name__ == '__main__':
    app.debug = True
    app.run(host = '0.0.0.0',port=5000)
  
and then it stopped. I mean google refuses to connect to 192.168.1.5:5000 and on the terminal I need to press CTRL+C twice to stop execution .and then I get the message:
Exception ignored in: <module 'threading' from '/usr/lib/python3.5/threading.py'>
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 1288, in _shutdown
t.join()
File "/usr/lib/python3.5/threading.py", line 1054, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.5/threading.py", line 1070, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):
I searched everywhere for an explanation but nothing works
Any ideas?

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Thu Feb 01, 2018 5:56 am

I am sorry about this.This line
t = threading.Thread(target=loop_func())
should have been
t = threading.Thread(target=loop_func)
That is what stopped the program working

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

Re: Robot web control and distance sensor working in parallel

Thu Feb 01, 2018 8:18 am

Hi, glad you sorted it. I hadn't got round to looking at your code but things like that are difficult to spot. Your code looks much tighter and easy to follow than your original, by the way. Did you use mmap inside your myfunc file? After my experiments I wasn't sure how much physical disk access it saved - I mean it might have been doing a good job but I couldn't figure a way of verifying it.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Sat Feb 03, 2018 6:10 pm

Thanks for following up.
I did try to use mmap but as you said, a) it needs a physical file to read and write and b) I could not move the file contents around functions.
I also tried
StringIO, that reads and writes a string buffer
( https://docs.python.org/2/library/stringio.html ) but I had no joy with that either.
The first part of my project (driving a robot around) is now complete and I will post a zip file with everything in Gdrive for those who may be interested.
Time to start with streaming a video from picamera.
Thanks again for your help and interest

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Sun Feb 11, 2018 7:25 am

paddyg, after a few days of abstinence from python, I thought you might be interested inn this:
Another way of shifting values around is to use the dictionary object which by its nature is a global object.
So, you can store key-value pairs in it and retrieve them very easily from anywhere in the code.
I have not tested this conclusively but so far it seems to work.
I hope that you might find this helpful.

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

Re: Robot web control and distance sensor working in parallel

Sun Feb 11, 2018 8:49 am

interesting idea, and I know that there is a buffering delay between file operations in code and the physical read write (which is why you have to eject drives before unplugging them) so it might not burn a hole in your SD card and be very quick.

For my app I want to pass a float value that is changing several times a second which might be a bit ungainly with frequent file creation and deletion. I will try a timeit test, but still not sure how to tell if it's working in the buffer or physically on disk.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

papous
Posts: 72
Joined: Fri Jan 05, 2018 5:50 am

Re: Robot web control and distance sensor working in parallel

Mon Feb 12, 2018 7:58 am

I was wrong about the dictionary object. It does not work
Sorry...

Return to “Python”