1

I am developing Flask rover app on Raspberry Pi Zero W. And I having problems with CPU perfomance. I used flask profiller to find weak spot in my app, but everything seemed normal. I have Raspberry Pi 3 and my app start significantly faster and CPU usages doesn't reach 90-100%. My Flask on Zero w app uses up to 64% cpu time. Should I use some lightweighter framework, maybe pure python? I get serious lags in my app. I know Pi3 has 4 cores, and zero W only one. But how I can get my app run faster?

from flask import Flask, render_template, Response, request, jsonify
import time # Import the Time library
import serial
import pigpio
from threading import Thread
import subprocess
from werkzeug.contrib.profiler import ProfilerMiddleware

SERVO_V = 21
SERVO_H = 20
pi = pigpio.pi()


MOT_FWD_A = 9
MOT_FWD_B = 10
MOT_BWD_A = 8
MOT_BWD_B = 7
SERVO_V = 21
SERVO_H = 20
LED = 4

FRQ = 1000
DUTY = 64


EMPTY_DATA = "0.0"

prevGyro = EMPTY_DATA
prevMetres = EMPTY_DATA
prevBatt = EMPTY_DATA

pi.set_PWM_dutycycle(MOT_FWD_A, 0)
pi.set_PWM_dutycycle(MOT_FWD_B, 0)
pi.set_PWM_dutycycle(MOT_BWD_A, 0)
pi.set_PWM_dutycycle(MOT_BWD_B, 0)

pi.set_PWM_frequency(MOT_FWD_A, FRQ)
pi.set_PWM_frequency(MOT_FWD_B, FRQ)
pi.set_PWM_frequency(MOT_BWD_A, FRQ)
pi.set_PWM_frequency(MOT_BWD_B, FRQ)

pi.set_PWM_dutycycle(LED, 0)
pi.set_PWM_frequency(LED, 50)


ser = serial.Serial(
               port='/dev/ttyS0',
               baudrate = 9600,
               timeout=1
           )

def setServoPosition(servoPin, newPosition):
        pi.set_servo_pulsewidth(servoPin, newPosition)
        print(newPosition)


def StopMotors():
    pi.set_PWM_dutycycle(MOT_FWD_A, 0)
    pi.set_PWM_dutycycle(MOT_FWD_B, 0)
    pi.set_PWM_dutycycle(MOT_BWD_A, 0)
    pi.set_PWM_dutycycle(MOT_BWD_B, 0)


# Turn both motors forwards AF = 1 AB = 0 BF = 1 BB = 0
def Forwards(): 
    print(DUTY)
    pi.set_PWM_dutycycle(MOT_FWD_A, DUTY)
    pi.set_PWM_dutycycle(MOT_FWD_B, DUTY)
    pi.set_PWM_dutycycle(MOT_BWD_A, 0)
    pi.set_PWM_dutycycle(MOT_BWD_B, 0)

def Backwards():
    pi.set_PWM_dutycycle(MOT_FWD_A, 0)
    pi.set_PWM_dutycycle(MOT_FWD_B, 0)
    pi.set_PWM_dutycycle(MOT_BWD_A, DUTY)
    pi.set_PWM_dutycycle(MOT_BWD_B, DUTY)

# Turn right
def Right():
    pi.set_PWM_dutycycle(MOT_FWD_A, 0) 
    pi.set_PWM_dutycycle(MOT_FWD_B, DUTY)
    pi.set_PWM_dutycycle(MOT_BWD_A, DUTY)
    pi.set_PWM_dutycycle(MOT_BWD_B, 0) 
    time.sleep(0.1)
    StopMotors()

# Turn left
def Left():
    pi.set_PWM_dutycycle(MOT_FWD_A, DUTY) 
    pi.set_PWM_dutycycle(MOT_FWD_B, 0)
    pi.set_PWM_dutycycle(MOT_BWD_A, 0)
    pi.set_PWM_dutycycle(MOT_BWD_B, DUTY)     
    time.sleep(0.1)
    StopMotors()



app = Flask(__name__)

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





@app.route("/info", methods=['GET'])
def telemetry():

    global prevBatt
    global prevGyro
    global prevMetres

    gyro = None
    battery = None
    metres = None

    data = ser.readline()
    #ser.flushInput()
    data_str = str(data, 'utf-8')
    #print(data)

    index_b = data_str.find('Bat')
    if (index_b is not -1):
        battery = data_str[index_b+4:index_b+9]
    else:
        battery = EMPTY_DATA


    index_g = data_str.find('gyro')
    if (index_g is not -1):
        gyro = data_str[index_g+5:index_g+11]
    else:
        gyro = EMPTY_DATA


    index_d = data_str.find('Distance')
    if (index_d is not -1):
        metres = data_str[index_d+9:index_d+15]
    else:
        metres = EMPTY_DATA



    if (index_g is not -1):
        prevGyro = gyro
    else:
        gyro = prevGyro

    if (index_b is not -1):
        prevBatt = battery
    else:
        battery = prevBatt

    if (index_d is not -1):
        prevMetres = metres
    else:
        metres = prevMetres

    #print("GYRO: %s\n" % (gyro))
    #print("BAT: %s\n"  % (battery))
    #print("MET: %s\n" %  (metres))

    return jsonify(voltage=battery,
                   gyro_x=gyro,
                   distance=metres)


@app.route("/control", methods=['GET'])
def remoteControl():
    global DUTY
    var =request.args.get('var')
    val = int(request.args.get('val'))
    print("var = %s \n val = %d" % (var, val)) 
    if (var == "car"):
        if (val == 1):
            Forwards()
        elif (val == 2):
            Left()
        elif  (val == 3):
            StopMotors()
        elif (val == 4):
            Right()
        elif (val == 5):
            Backwards()

    elif (var == "servo"):
        if (val < 1250):
            setServoPosition(SERVO_V, 1250)
        elif (val > 2500):
            setServoPosition(SERVO_V, 2500)
        else:
            setServoPosition(SERVO_V, val)

    elif (var == "servo1"):
        if (val < 500):
            setServoPosition(SERVO_H, 500)
        elif (val > 2300):
            setServoPosition(SERVO_H, 2300)
        else:
            setServoPosition(SERVO_H, val)

    elif (var == "speed"):
        if (val > 240):
            DUTY = 240
        elif (val < 0):
            DUTY = 0
        else:
            DUTY = val
    elif (var == "led"):
         if (val > 255):
             pi.set_PWM_dutycycle(LED,255)
         elif (val < 0):
             pi.set_PWM_dutycycle(LED, 0)
         else:
             pi.set_PWM_dutycycle(LED, val)

    return('', 204)




def clientIsHere():
    while(1):
        cmd_out = subprocess.run(['hostapd_cli', 'all_sta'], stdout=subprocess.PIPE).stdout.decode('utf-8')
        if (not len(cmd_out) > 27):
            StopMotors()
        time.sleep(0.5)



if __name__ == '__main__':
    t = Thread(target=clientIsHere)
    t.start()
    app.config['PROFILE'] = True
    app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions = [30])
    app.run(debug=True, host='0.0.0.0', threaded=True)
    time.sleep(0.5)
  • 1
    It's not the framework but your software architecture. You are servicing serial port and motor control synchronously with every request, so your application will block while it waits for serial port communications and returns from ioctl call, if multiple requests stack up you are perpetually waiting for serial port data when you should simply serve the last available or cached data . You should probably decouple the two and not do any serial port logic in the web request function. Usually this takes multi threaded design – crasic Nov 26 '19 at 21:56
  • As a simple test, reduce the serial timeout and see if responsiveness improves (but with more glitches/missed data) – crasic Nov 27 '19 at 2:04
1

The Flask framework is fine, your code has all the right logic and procedures as far as I can tell. But the structure (architecture) and control flow is not optimized for quick response or for multiple connections.

The problem is that all the control logic (with hardware interactions) happens during the request/response processing. This is fine for a development/debug utility or something not meant to be accessed very often.,

Depending on how Flask is configured, each requester may be trying to access the same resource (e.g Serial or GPIO) and clobber each other at worst or spend a long time waiting.

Your control flow now (taking the serial info as example).

FLASK THREAD     
------------

-->REQUEST
FLASK CALLBACK
SERIAL IO (May take up to 1s + more for writes and retries)
PROCESS DATA  
RESPONSE-->

Depending on the timekeeping and how the serial IO library is written, the thread may be held "responsible" for the use of CPU while waiting for serial IO, in either case, for latency/response time the same amount of time is required and CPU load is not really all that important (only turnaround time)

The suggestion is to create a way to separate the device logic from the Flask logic

FLASK THREAD                     RPI WORKER THREAD
------------                     -----------------

-->REQUEST 
FLASK CALLBACK
SERIAL EVENT -----------------------> WAKE
<--RESPONSE (Not yet available)       READ SERIAL
                                      PROCESS DATA
                                      POPULATE RESPONSE BUFFER
                                      SLEEP

-->REQUEST
FLASK CALLBACK
<--RESPONSE (BUFFER)

This means that some of the "waiting" is put on the client, typically for a web app or API this is handled with some javascript or other DOM scripting that causes a refresh or reload (re-request)

It also allows for a single location to handle conflicts and to interleave device access.

Remember there is only one serial port at a time, so no matter how good the framework is, each user will have to wait for the serial data to be read, at 9600 baud this is not particularly fast, compared to the response times considered satisfactory for a web client or api.

For additional suggestions I would recommend https://codereview.stackexchange.com

| improve this answer | |
  • Thanks for detailed answer. – user111619 Nov 27 '19 at 8:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.