Ajax POST to Flask not working after page refresh

Viewed 1083

I'm improving a system for controlling a telescope remotely. A Raspberry Pi runs flask, and provides a video stream for a camera attached to the telescope. The telescope's focuser is actuated by a stepper motor controlled with an Arduino.The server provides a website that shows the video stream, and offers two buttons to move the focuser in and out.

When either button is clicked, the client sends a POST to the RasPi, and then the RasPi tells the Arduino to move the focuser. But crucially I did not want the page to refresh while refocusing. Hence, I used jQuery and Ajax to suppress the page refresh.

The relevant code snippets are here:

Python/Flask code:

@app.route('/stream/<wcam>', methods=['GET'])
def stream_get(wcam):
    class FocuserForm(FlaskForm):
        nsteps = IntegerField('# steps: ', default=1)
        focuser_in = SubmitField('Focuser in')
        focuser_out = SubmitField('Focuser out')

    form = FocuserForm()
    return render_template('stream.html', wcam=wcam, form=form)

@app.route('/stream/<wcam>', methods=['POST'])
def stream_post(wcam):
    results = request.form
    arduino_serial = SerialFocuser()
    if results['caller'] == "focuser_in":
        command = "MVD" + results['steps'] + "\n"
        arduino_serial.send_command(command)
    elif results['caller'] == "focuser_out":
        command = "MVU" + results['steps'] + "\n"
        arduino_serial.send_command(command)
    return ''

Web (stream.html):

<html>
    <head>
        <title>Video Streaming</title>
        <style>
            ...
        </style>

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

        <script>
                $(document).ready(function() {});
        </script>
      </head>

    <body>
        <h1>Streaming camera {{ wcam }}</h1>
        <br>
        <img id="bg" src="{{ url_for('video_feed', wcam=wcam) }}", height="480" width="640">
        <a href="{{ url_for('index') }}">Back</a>
        <br>


    <!--######################################################-->
    <!--#  Focuser handling -->
    <!--######################################################-->

    <br>
    <form id="flaskform" method="POST">
        <p>
        {{ form.nsteps.label }} {{ form.nsteps() }} &nbsp;
        {{ form.focuser_in() }} &nbsp;
        {{ form.focuser_out() }}
        </p>
    </form>

    <script>
    // $(document).ready(function() { // Moved to header

            var form = document.getElementById('flaskform');
            function onSubmit(event) {
                console.log('onSubmit function');
                var objectID = event.explicitOriginalTarget.id;
                var nsteps = form.nsteps.value;
                var return_data = {caller: "", steps: nsteps};
                if (objectID == "focuser_in") {
                    return_data.caller = objectID;
                    console.log("Focuser_in detected");
                } else if (objectID == "focuser_out") {
                    return_data.caller = objectID;
                    console.log("Focuser_out detected");
                } else if (objectID == "nsteps") {
                    console.log("nsteps detected");
                    event.preventDefault();
                    return;
                } else {
                    console.log("No matches");
                    return;
                }
                console.log("About to run Ajax");
                $.ajax({
                    url: "stream.html",
                    type: "post",
                    data: return_data,
                    success: function(response) {
                        console.log('It worked!');
                    },
                    error: function(xhr, status, text) {
                        console.log('An error occurred:', status,"; ", text);

                    },
                    timeout: 1000 // 1s
                }); // Ajax
                console.log("After running Ajax");
                if (event) { event.preventDefault(); }

            }
            // prevent when a submit button is clicked
            form.addEventListener('submit', onSubmit, false);

            //<!--form.addEventListener('submit', onSubmit, false);-->
            // prevent submit() calls by overwriting the method
            form.submit = onSubmit;

    //});  // Moved to header
    </script>
    </body>
</html>

The problem is as follows:

If I refresh the page on the client's browser and then click a button, ajax does POST, but flask does not seem to receive it. The request times out.

If I now restart the server (I'm developing this with PyCharm, so I just click re-run) without refreshing the page in the client, and then click a button, flask does get the POST, and the focuser works like a charm.

If I refresh the page again, then the buttons stop working until I reset the server.

Why does this happen? Obviously the code works in its main purpose, but somehow the page refresh is breaking something.

2 Answers

I had a similar issue once with a camera thread blocking all calls. When you reset the server, does your camera feed still run (before clicking the button)? Because basically you are calling your camera feed twice - first with the get call when you refresh your page, then again with the post call.

I'd advice you to refactor your submitted code into an alternative function for clarity:

@app.route('/stream/<wcam>', methods=['POST'])
def moveCommand:
       if form.is_submitted():
    # POST method
    results = request.form
    arduino_serial = SerialFocuser()
    if results['caller'] == "focuser_in":
        command = "MVD" + results['steps'] + "\n"
        arduino_serial.send_command(command)
    elif results['caller'] == "focuser_out":
        command = "MVU" + results['steps'] + "\n"
        arduino_serial.send_command(command)

So basically you keep your get method for only the streaming and use the post for the moving around.

Thanks to @Peter van der Wal for pointing me towards the solution.

The video streamer has a while True loop, which continually takes frames from the camera, hence locking the thread.

The solution was to start the app with the threaded option on:

Before:

app.run(host='0.0.0.0', debug=True)

Now:

app.run(host='0.0.0.0', debug=True, threaded=True)

This allows the video streaming thread to continue on its own, while allowing the server to process other commands.

Related