BottlePy and PyWebView - python-3.x

I am trying to build a small app in Bottle, and thought I'd try using pywebview as a viewer. When I run the following file, I get two instances of the webview window. The first one shows the page, the second shows a spinning wheel cursor. Closing the second window is shutting down the web server, I believe, but not killing the thread.
Why are there two windows showing up?
import sys
import threading
from bottle import Bottle, ServerAdapter
import webview
class MyWSGIRefServer(ServerAdapter):
server = None
def run(self, handler):
from wsgiref.simple_server import make_server, WSGIRequestHandler
if self.quiet:
class QuietHandler(WSGIRequestHandler):
def log_request(*args, **kw): pass
self.options['handler_class'] = QuietHandler
self.server = make_server(self.host, self.port, handler, **self.options)
self.server.serve_forever()
def stop(self):
# self.server.server_close() <--- alternative but causes bad fd exception
self.server.shutdown()
app = Bottle()
listen_addr = 'localhost'
listen_port = 8080
server = MyWSGIRefServer(host='localhost', port=8080)
#app.route('/')
def hello():
return "Hello World!"
def start_server():
app.run(server=server, reloader=True)
try:
print(threading.enumerate())
serverthread = threading.Thread(target=start_server)
serverthread.daemon = True
print("starting web server")
serverthread.start()
print("starting webview")
webview.create_window('bottle test', "http://localhost:8080/")
print("webview closed. closing server")
sys.exit()
server.stop()
except Exception as ex:
print(ex)

The issue was using reloader=True when running the server. Setting this to False prevents the second window from appearing.

Related

Python Serial "ClearCommError failed (OSError(9, 'The handle is invalid.', None, 6))

I am new to python and multiprocessing (Electronics background). I am writing an GUI application to communicate with micro controller over serial port. The interaction with user is through some buttons on the GUI, which sends some command on serial port to the MCU and displays data received from MCU in the same GUI(there can be data from MCU without any command being sent). I am using PyQt5 module and QT designer to design the UI. I converted the UI to py code using uic.compileUi. I am then calling this python file in my main code.
After reading through several documents and stack overflow posts I decided to use to QThreads for the application.
I am however facing issue with serial communication when being used inside thread (It works fine if used in a single main loop without threads).
My code is:
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication,QWidget,QMainWindow #
from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal, pyqtSlot, QThread
import serial
from GUI import Ui_MainGUIWindow
import sys
serialString = ""
def ClosePort():
serialPort.close()
# print("port closed")
def OpenPort():
serialPort.open()
#print("port open")
def sendFV():
serialPort.write('fv\r'.encode())
def configSerial(port,baudRate):
global serialPort
serialPort=serial.Serial(port = port, baudrate=baudRate, bytesize=8, timeout=None, stopbits=serial.STOPBITS_ONE)
if not serialPort.isOpen():
serialPort.open()
class Worker(QObject):
finished = pyqtSignal()
dataReady = pyqtSignal(['QString'])#[int], ['Qstring']
print("workerinit")
def run(self):
print ("worker run")
while(1):
if(serialPort.in_waiting > 0):
serialString = serialPort.read()
self.dataReady.emit(str(serialString))
self.finished.emit()
class GUI(QWidget):
def __init__(self):
self.obj = Worker()
self.thread = QThread()
self.obj.dataReady.connect(self.onDataReady)
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.started.connect(self.obj.run)
self.thread.finished.connect(app.exit)
self.thread.start()
self.initUI()
def initUI(self):
#Form, Window = uic.loadUiType("TestGUI.ui")
#print(Form,Window)
#self.window = Window()
#self.form=Form()
self.window = QMainWindow()
self.form = Ui_MainGUIWindow()
self.form.setupUi(self.window)
self.form.FVButton.clicked.connect(sendFV)
print("guiinit")
self.window.show()
def onDataReady(self, serialString):
print("here")
print(serialString.decode('Ascii'))
self.form.plainTextOutput.insertPlainText((serialString.decode('Ascii')))
self.form.plainTextOutput.ensureCursorVisible()
if __name__ == '__main__':
configSerial("COM20",9600)
"""
app = QCoreApplication.instance()
if app is None:
app = QCoreApplication(sys.argv)
"""
app = QApplication(sys.argv)
form=GUI()
app.exec_()
sys.exit(ClosePort())
I get following error on line if(serialPort.in_waiting > 0):
File "C:\Users\...\Anaconda3\lib\site-packages\serial\serialwin32.py", line 259, in in_waiting
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
SerialException: ClearCommError failed (OSError(9, 'The handle is invalid.', None, 6))
I found this post talking about similar issue, but do know how exactly to implement the suggested solution in my code.
Also I get "kernel died" error multiple times when I run the code on spyder IDE. I added a check if I am creating multiple instances of QT application but that did not help.Running the code from anaconda command prompt works fine.

How to Access pywebview.Window Object from Another multiprocessing.Process?

I have a webview that controlling the flask api.
The webview will have a button to start the flask server and a button to stop the server. That is why I have to use multiprocessing.Process to create a separate process for Flask. With that, I cannot access my pywebview.Window anymore. I want to use pywebview.Window to evaluate some javascript with pywebview.Window.evaluate_js() within the Flask process (of course it has to be the same pywebview.Window that I already created before open a new process for Flask).
Is anybody know how to accomplish this issue. I appreciate it!
Some sample code:
from flask import Flask, request, jsonify
import os, sys, re, json, socket, sqlite3, base64, requests, webview
from flask_cors import CORS
class ServerFlaskApi:
def __init__(self):
self.app = Flask(__name__, root_path=Root_Dir)
self.app.add_url_rule("/", view_func=self.Default)
def Default(self):
return "Welcome to the Python Http Server for your Application!", 200
def PrintToWebViewConsole(self):
#Trying to use pywebview.Window here, of course WebviewWindow is not defined!!!
WebviewWindow.evaluate_js(js_script)
################
class WebviewApi:
def __init__(self):
self.server_thread = None
def StartServer(self):
self.server_thread = multiprocessing.Process(target=Run_Flask_Server, daemon=True)
self.server_thread.start()
def StopServer(self):
self.server_thread.terminate()
def Run_Flask_Server():
serverApi = ServerFlaskApi()
CORS(serverApi.app)
serverApi.app.run(host=Server_Host, port=Server_Port, debug=True, use_reloader=False)
################
if __name__ == "__main__":
WebViewApi = WebviewApi()
WebviewWindow = webview.create_window(title="Server Monitor", url="view/main-gui.html", js_api=WebViewApi, width=550, height=750, min_size=(550, 750), resizable=False, on_top=True, confirm_close=False)
webview.start(debug=False)
I'm still new in Python, so any suggestion is welcome!
Thank you in advance!
I guess I have to use Threading instead of Processing, since Thread is sharing memory and Process is not.
Also, for anybody who want to stop a Thread, here is a function to do that, not sure if this is a good way to do it, but it does the job for me:
def Kill_Thread(thread):
if not isinstance(thread, threading.Thread):
raise TypeError("Must be set as threading.Thread type!!!")
thread_id = thread.ident
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
if res > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
print("Exception raise failure")

Shutdown RPyC server from client

I've created a RPyC server. Connecting works, all my exposed methods work. Now I am looking to shut down the server from the client. Is this even possible? Security is not a concern as I am not worried about a rogue connection shutting down the server.
It is started with (Which is blocking):
from rpyc import ThreadPoolServer
from service import MyService
t = ThreadPoolServer(MyService(), port=56565)
t.start()
Now I just need to shut it down. I haven't found any documentation on stopping the server.
You can add to your Service class the method:
def exposed_stop(self):
pid = os.getpid()
if platform.system() == 'Windows':
PROCESS_TERMINATE = 1
handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, pid)
ctypes.windll.kernel32.TerminateProcess(handle, -1)
ctypes.windll.kernel32.CloseHandle(handle)
else:
os.kill(pid, signal.SIGTERM)
This will make the service get its own PID and send SIGTERM to itself. There may be an better way of doing this hiding in some dark corner of the API, but I've found no better method.
If you want to do clean-up before your thread terminates, you can set up exit traps:
t = rpyc.utils.server.ThreadedServer(service, port=port, auto_register=True)
# Set up exit traps for graceful exit.
signal.signal(signal.SIGINT, lambda signum, frame: t.close())
signal.signal(signal.SIGTERM, lambda signum, frame: t.close())
t.start() # blocks thread
# SIGTERM or SIGINT was received and t.close() was called
print('Closing service.')
t = None
shutil.rmtree(tempdir)
# etc.
In case anybody is interested, I have found another way of doing it.
I'm just making the server object on a global scope, and then adding an exposed method to close it.
import rpyc
from rpyc.utils.server import ThreadedServer
class MyService(rpyc.Service):
def exposed_stop(self):
server.close()
def exposed_echo(self, text):
print(text)
server = ThreadedServer(MyService, port = 18812)
if __name__ == "__main__":
print("server start")
server.start()
print("Server closed")
On the client side, you will have an EOF error due to the connection being remotely closed. So it's better to catch it.
import rpyc
c = rpyc.connect("localhost", 18812)
c.root.echo("hello")
try :
c.root.stop()
except EOFError as e:
print("Server was closed")
EDIT: I needed to be able to dinamically specify the server. So I came with this (Is it better ? I don't know, but it works well. Be careful though, if you have multiple server running this service: things could become weird):
import rpyc
from rpyc.utils.server import ThreadedServer
class MyService(rpyc.Service):
_server:ThreadedServer
#staticmethod
def set_server(inst=ThreadedServer):
MyService._server = inst
def exposed_stop(self):
if self._server:
self._server.close()
def exposed_echo(self, text):
print(text)
if __name__ == "__main__":
server = ThreadedServer(MyService, port = 18812)
MyService.set_server(server)
print("server start")
server.start()
print("Server closed")
PS: It probably is possible to avoid the EOF error by using Asynchronous Operations

running 4 functions periodically in the background without freezing the GUI

I made a pyqt5 script which uses python3.6
my scripts takes input from serial device and updates the GUI and the remote database and also take remote input from web browser and make necessary changes to the serial device and update the GUI accordingly.
I am using QTimer to run the 4 functions/methods in the background to achieve this and it's working. But the problem is that after receiving a manual input or remote input (from web browser) it takes almost 15-20 seconds to implement the necessary changes in the serial device and the GUI and it makes the GUI almost unresponsive (it responds after almost 15-20 seconds).
I tried QThread but it always results in multiple access on the serial port at the same time causing the script to crash. Please, help me in this regard.
from PyQt5.QtCore import QTimer, QThread
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUiType
import os
import sys
import time
import serial
import MySQLdb
from pathlib import Path
import json
ui, _ = loadUiType('myapplication.ui')
class MainApp(QMainWindow, ui):
def __init__(self):
QMainWindow.__init__(self)
self.setupUi(self)
def new_method1(self):
print("This is new_method1")
def new_method2(self):
print("This is new_method2")
def new_method3(self):
print("This is new_method3")
def new_method4(self):
print("This is new_method4")
def continuous_calling(self):
self.timer = QTimer()
self.timer.timeout.connect(self.new_method1)
self.timer.start(1000)
self.timer.timeout.connect(self.new_method2)
self.timer.start(1000)
self.timer.timeout.connect(self.new_method3)
self.timer.start(1000)
self.timer.timeout.connect(self.new_method4)
self.timer.start(1000)
def main():
app = QApplication(sys.argv)
window = MainApp()
window.move(0, -30)
window.show()
window.continuous_calling()
app.exec_()
if __name__ == '__main__':
main()
Here, i want to run new_method1, new_method2, new_method3 & new_method4 methods continuously in the background as long as the GUI is not exiting without freezing the GUI. Thanks.
additional information::
This is an example of other functions that manages direct user inputs from GUI(button clicks from the GUI). And these functions don't run in the background repeatedly. These functions are triggered only when buttons(in the GUI) are clicked.
def ser_conn_on_zero(self):
self.ser = serial.Serial(
port=self.comm_port,
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
timeout=1)
if self.ser.isOpen():
print("Open: " + self.ser.name)
self.ser.flushInput()
self.ser.flushOutput()
self.ser.close()
self.ser.open()
print('Connected to :' + self.comm_port)
print("Turning ON Relay Zero")
self.ser.write(b'\x46\x6D\x45\x63\x73\x4B\x6F\x6C\x00\x05\x00\x01\x00\x01\x00\r\n')
output = self.ser.readline().decode('iso-8859-1')
print("Serial Response: " + output)
self.ser.close()
print("Serial connection Closed for Relay Zero")
conn = MySQLdb.connect("XX.XX.XX.XX", "User2", "PASSWORD", "TestSite_001", use_unicode=True, charset="utf8")
cursor = conn.cursor()
try:
cursor.execute("update some_table set chn = %s where id = %s", (1, 1))
conn.commit()
except MySQLdb.Error as e:
print("ser_conn_on_zero: couldn't update the remote database : ", str(e))
cursor.close()
conn.close()

Flask versus PyQt5 - Flask controls program flow?

Flask appears to prevent PyQt5 UI from updating.
The respective code works properly for either PyQt5 or Flask - but not together. I understand that it may need to do with the way threading is set up.
Any assistance would be greatly appreciated. TIA.
`
import sys
import serial
import threading
from PyQt5.QtWidgets import QWidget, QLabel, QApplication
from flask import Flask, render_template, request, redirect, url_for
app1 = Flask(__name__)
ser = serial.Serial ("/dev/ttyS0", 57600,timeout=3) #Open port with baud rate
count=0
temp = []
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
global count
count = 1
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('PyQt5 vs Flask')
self.lbl1 = QLabel('Count '+str(count), self)
self.lbl1.move(100, 50)
self.show()
threading.Timer(5,self.refresh).start()
def refresh(self):
global count
count +=1
print("UI ",count)
self.lbl1.setText('Count'+str(count))
threading.Timer(5,self.refresh).start()
def get_uart():
global temp
if ser.inWaiting()>0:
temp =[str(float(x.decode('utf-8'))) for x in ser.read_until().split(b',')]
print(temp)
threading.Timer(1,get_uart).start()
#app1.route("/")
def index():
global temp
templateData = {'temp1' : temp[1] ,'temp2' : temp[2]}
return render_template('index.html',**templateData)
if __name__ == "__main__":
app = QApplication(sys.argv)
pyqt5 = Example()
threading.Timer(1,get_uart).start()
ser.flushInput()
#app1.run(host='0.0.0.0',threaded=True, port=5000) # ,debug=True)
sys.exit(app.exec_())
`
Need to have a UI to control the data analysis to be displayed on Website.
[SOLVED]
All Flask parameters can be defined as:
port = int(os.environ.get('PORT', local_port))
kwargs = {'host': '127.0.0.1', 'port': port , 'threaded' : True, 'use_reloader': False, 'debug':False}
threading.Thread(target=app.run, daemon = True, kwargs=kwargs).start()
and Flask will NOT Block and run with the parameters defined in kwargs.
The better way deal with (possibly waiting) processes, is to use Qt's own threads.
In this example I've created a QObject subclass that does all processing and eventually sends a signal whenever the condition is valid. I can't install flask right now, so I've not tested the whole code, but you'll get the idea.
The trick is to use a "worker" QObject that does the processing. Once the object is created is moved to a new QThread, where it does all its processing without blocking the event loop (thus, the GUI).
You can also create other signals for that object and connect to your slots (which might also be standard python functions/methods outside the QtApplication) which will be called whenever necessary.
class Counter(QtCore.QObject):
changed = QtCore.pyqtSignal(str)
def __init__(self):
super().__init__()
self.count = 0
def run(self):
while True:
self.thread().sleep(1)
if ser.inWaiting() > 0:
self.changed.emit('{}: {}'.format(self.count, [str(float(x.decode('utf-8'))) for x in ser.read_until().split(b',')]))
self.count += 1
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.counter = Counter()
self.counterThread = QtCore.QThread()
self.counter.moveToThread(self.counterThread)
self.counterThread.started.connect(self.counter.run)
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('PyQt5 vs Flask')
self.lbl1 = QtWidgets.QLabel('Count {}'.format(self.counter.count), self)
self.lbl1.move(100, 50)
self.counter.changed.connect(self.lbl1.setText)
self.counterThread.start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
pyqt5 = Example()
pyqt5.show()
I think the problem stems from how Flask is activated. If the app.run command is given any parameters (even if in a Thread), then it blocks other commands.
The only way I was able to make Flask and PyQt5 work at the same time, was to activate Flask in a dedicated Thread WITHOUT any parameters - SEE BELOW for the various combinations.
Question: Is this a Flask/Python Bug or Feature or some other explanation related to Development vs Production deployment??
In any case, I would like any help with finding a way to deploy flask in a Port other than 5000 - WITHOUT Flask Blocking other code.
import sys
import serial
import threading
import atexit
from PyQt5.QtWidgets import QWidget, QLabel, QApplication
from flask import Flask, render_template, request, redirect, url_for
ser = serial.Serial ("/dev/ttyS0", 57600,timeout=3) #Open port with baud rate
app = Flask(__name__)
count=0
temp = []
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
global count
count = 1
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('PyQt5 vs Flask')
self.lbl1 = QLabel("Count "+str(count)+" ", self)
self.lbl1.move(100, 50)
self.show()
threading.Timer(5,self.refresh).start()
def refresh(self):
global count
global str_data
count +=1
self.lbl1.setText("Count "+str(count)+" ")
threading.Timer(0.5,self.refresh).start()
def get_uart():
global temp
if ser.inWaiting()>0:
temp =[str(float(x.decode('utf-8'))) for x in ser.read_until().split(b',')]
print(temp)
threading.Timer(1,get_uart).start()
#app.route("/")
def blank():
global count
data="Count "+str(count)
return data
if __name__ == "__main__":
threading.Timer(5,get_uart).start()
#app.run ## Does not block further execution. Website IS NOT available
#app.run() ## Blocks further execution. Website available at port 5000 without Refresh value
#app.run(port=5123) ## Blocks further execution. Website available at port 5123 without Refresh value
#app.run(threaded=True) ## Blocks further execution. Website available at port 5000 without Refresh value
#threading.Thread(target=app.run()).start() ## Blocks further execution. Website available at port 5000 without Refresh value
#threading.Thread(target=app.run(port=5123)).start() ## Blocks further execution. Website available at port 5123 without Refresh value
#threading.Thread(target=app.run(threaded=True)).start() ## Blocks further execution. Website available at port 5000 without Refresh value
threading.Thread(target=app.run).start() ## Flask DOES NOT block. Website is available at port 5000 with Refresh value
print("Flask does not block")
app1 = QApplication(sys.argv)
pyqt5 = Example()
sys.exit(app1.exec_())

Resources