3

I want to import some function in form of a script, let's call it controller.py, to a flask app as a web service. Let's call the flask app api.py.

The problem is, in controller.py, there is a pyserial declaration.

controller.py:

import serial

ser = serial.Serial('COM31', 9600,timeout=2)

def serial_function(foo):
    ser.write(foo)
    reply = ser.read()
    return reply

api.py:

from flask import Flask
import controller as cont

app = Flask(__name__)

@app.route('/function/<foo>',methods=['GET'])
def do_function(foo):
    data=cont.serial_function(foo)
    return data

if __name__ == '__main__':
    app.run('0.0.0.0', 80,True)

But i got this error:

raise SerialException("could not open port %s: %s" % (self.portstr, ctypes.WinError()))
serial.serialutil.SerialException: could not open port COM31: [Error 5] Access is denied.

It seems that Flask is trying to import controller.py over and over again, and the serial port re initialized.

Is there some way I can achieve what I'm trying to do as described above?

aschultz
  • 1,658
  • 3
  • 20
  • 30

1 Answers1

0

The main problem of your code is that the Serial object creation is directly in the code of your module. This way, each time you import your module, python will load/interpret the file, and by doing this, will execute all the code found at the root level of your module. (you can refer to this excellent answer if you want to dig in more : What does if __name__ == "__main__": do?)

Moreover, in debug mode, Flask will start 2 processes (one to monitor the source code and when changed, restart the second one, which is the one who will really handle the requests) and in production mode, you could create much more threads or processes when starting the server, by doing this, your module is imported at least twice => conflict in serial open.

A possible solution would be to remove the initialization of the serial port from your module and use the context manager syntax in your method :

def serial_function(foo):
    with serial.Serial('COM31', 9600,timeout=2) as ser:
        ser.write(foo)
        reply = ser.read()
        return reply

This way, you will open (and close) your serial port at each read.

But, you'll still have to deal with concurrent access if you have multiple clients making requests to your webserver simultaneously.

EDIT: If, as you say in comment, you need to open only once your serial port, you'll need to encapsulate this in a specific object (probably using a Singleton pattern) that will be responsible of opening the serial port if not already opened:

class SerialProxy:
    def __init__(self):
        self.__serial = None

    def serial_function(self, foo):
        if self.__serial is None:
            self.__serial = serial.Serial('COM31', 9600,timeout=2)
        self.__serial.write(foo)
        reply = self.__serial.read()
        return reply
Cédric Julien
  • 78,516
  • 15
  • 127
  • 132
  • thanks for the answer but is there solution that doesn't re open serial at each calling. i want this script to communicate with arduino or any other MCU else which restart itself every opening of serial communication. – Alvin Hardian Aug 19 '19 at 14:56
  • @AlvinHardian : I updated my answer according to this new information (add it in your original question ;) ) – Cédric Julien Aug 19 '19 at 15:26