Reputation: 135
Is it possible to keep a UDP server running as an asynchronous function receiving data and then passing it to an (PyQt5) widget which is also running as an asynchronous function??
The idea is that when the data coming into the server is updated, it also updates the widget.
I have got a simple UDP server and a (PyQt5) widget already which are working fine independently but I am struggling trying to combine them and keep them both running asynchronously and exchanging data(Server transmitting data to widget)
[UPDATE]
Below is a widget that I am trying out
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMainWindow
import asyncio
class Speedometer(QMainWindow):
angleChanged = QtCore.pyqtSignal(float)
def __init__(self, parent = None):
QtWidgets.QWidget.__init__(self, parent)
self._angle = 0.0
self._margins = 20
self._pointText = {0: "40", 30: "50", 60: "60", 90: "70", 120: "80",
150:"" , 180: "", 210: "",
240: "0", 270: "10", 300: "20", 330: "30", 360: ""}
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.fillRect(event.rect(), self.palette().brush(QtGui.QPalette.Window))
self.drawMarkings(painter)
self.drawNeedle(painter)
painter.end()
def drawMarkings(self, painter):
painter.save()
painter.translate(self.width()/2, self.height()/2)
scale = min((self.width() - self._margins)/120.0,
(self.height() - self._margins)/60.0)
painter.scale(scale, scale)
font = QtGui.QFont(self.font())
font.setPixelSize(10)
metrics = QtGui.QFontMetricsF(font)
painter.setFont(font)
painter.setPen(self.palette().color(QtGui.QPalette.Shadow))
i = 0
while i < 360:
if i % 30 == 0 and (i <150 or i > 210):
painter.drawLine(0, -40, 0, -50)
painter.drawText(-metrics.width(self._pointText[i])/2.0, -52,
self._pointText[i])
elif i <135 or i > 225:
painter.drawLine(0, -45, 0, -50)
painter.rotate(15)
i += 15
painter.restore()
def drawNeedle(self, painter):
painter.save()
painter.translate(self.width()/2, self.height()/1.5)
painter.rotate(self._angle)
scale = min((self.width() - self._margins)/120.0,
(self.height() - self._margins)/120.0)
painter.scale(scale, scale)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(self.palette().brush(QtGui.QPalette.Shadow))
painter.drawPolygon(
QtGui.QPolygon([QtCore.QPoint(-10, 0), QtCore.QPoint(0, -45), QtCore.QPoint(10, 0),
QtCore.QPoint(0, 5), QtCore.QPoint(-10, 0)])
)
painter.setBrush(self.palette().brush(QtGui.QPalette.Highlight))
painter.drawPolygon(
QtGui.QPolygon([QtCore.QPoint(-5, -25), QtCore.QPoint(0, -45), QtCore.QPoint(5, -25),
QtCore.QPoint(0, -30), QtCore.QPoint(-5, -25)])
)
painter.restore()
def sizeHint(self):
return QtCore.QSize(150, 150)
def angle(self):
return self._angle
# @pyqtSlot(float)
def setAngle(self, angle):
if angle != self._angle:
self._angle = angle
self.angleChanged.emit(angle)
self.update()
angle = QtCore.pyqtProperty(float, angle, setAngle)
@staticmethod
def mainLoopSpd():
while True:
app = QApplication(sys.argv)
window = QtWidgets.QWidget()
spd = Speedometer()
spinBox = QtWidgets.QSpinBox()
#spd.setAngle(100)
spinBox.setRange(0, 359)
spinBox.valueChanged.connect(spd.setAngle)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(spd)
layout.addWidget(spinBox)
window.setLayout(layout)
window.show()
app.exec_()
#await asyncio.sleep(1)
sys.exit(app.exec_())
Below is an implementation of a UDP socket end which also is printing the values in the console
import socket
class UDPserver:
def __init__(self, parent= None):
self.localIP = "127.0.0.1"
self.localPort = 20002
self.bufferSize = 1024
self.UDPServerSocket = socket.socket(family= socket.AF_INET, type=socket.SOCK_DGRAM) # Create a socket object
self.UDPServerSocket.bind((self.localIP, self.localPort))
print("UDP server up and listening")
self.counter= 1
@staticmethod
def mainLoopUDPserver():
serv= UDPserver()
#while(True):
bytesAddressPair = serv.UDPServerSocket.recvfrom(serv.bufferSize) # Receive data from the socket
message = bytesAddressPair[0] # The output of the recvfrom() function is a 2-element array
# First element is the message
address = bytesAddressPair[1] # Second element is the address of the sender
newMsg= "{}".format(message)
serv.counter=serv.counter+1
NumMssgReceived = "#Num of Mssg Received:{}".format(serv.counter)
newMsg= newMsg.replace("'","")
newMsg= newMsg.replace("b","")
newMsg= newMsg.split("/")
eastCoord= float(newMsg[0])
northCoord= float(newMsg[1])
vehSpeed= float(newMsg[2])
agYaw= float(newMsg[3])
eastCoordStr="East Coordinate:{}".format(newMsg[0])
northCoordStr="North Coordinate:{}".format(newMsg[1])
vehSpeedStr= "Vehicle Speed:{}".format(newMsg[2])
agYawStr="Yaw Angle:{}".format(newMsg[3])
print(NumMssgReceived)
print(vehSpeedStr)
and below is the main function which is calling them both
from speedometer import Speedometer
import asyncio
from pyServer import UDPserver
class mainApp:
#vel = 0
def __init__(self):
self.velo = 0
self.queue= asyncio.Queue(0)
async def server(self):
while True:
self.velo= UDPserver.mainLoopUDPserver()
print("THIS IS VELO{}",self.velo)
#await self.queue.put(self.velo)
#vel= await self.queue.get()
#return vel
#print("ASSDASDSADSD{}",vel)
await asyncio.sleep(0)
#print("HI, vel Received={}",self.veloc)
#return velo
async def widget(self):
while True:
#vel = await self.queue.get()
#print("Hola xDDDDDDD", vel)
print(">>>>>>>>>>>>>>>NextIteration>>>>>>>>>>>>>>")
await Speedometer.mainLoopSpd()
await asyncio.sleep(0)
loop= asyncio.get_event_loop()
mApp= mainApp()
loop.create_task(mApp.server())
loop.create_task(mApp.widget())
loop.run_forever()
So, when I run it, it listens to the server and once I start sending data over UDP, it receives the first piece of data and open the widget which is runs just fine but it makes the server to stop, it does not receive any data anymore.
As you can see in the comments, I have also been playing around with Asyncio queues, but I haven't got anything really.
My ideal scenario would be the server receiving the data and passing it to the widget so it gets updated with the incoming data but for now I just want them both working independently.
Thanks
Upvotes: 9
Views: 17639
Reputation: 244132
It should be clear that your UDP server does not run asynchronously.
The logic of asyncio is that everything uses an eventloop as a base, and by default Qt does not support it, so you must use libraries such as qasync
(python -m pip install qasync
) and asyncqt
(python -m pip install asyncqt
)
Considering the above, the solution is:
speedometer.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Speedometer(QtWidgets.QWidget):
angleChanged = QtCore.pyqtSignal(float)
def __init__(self, parent=None):
super().__init__(parent)
self._angle = 0.0
self._margins = 20
self._pointText = {
0: "40",
30: "50",
60: "60",
90: "70",
120: "80",
150: "",
180: "",
210: "",
240: "0",
270: "10",
300: "20",
330: "30",
360: "",
}
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.fillRect(event.rect(), self.palette().brush(QtGui.QPalette.Window))
self.drawMarkings(painter)
self.drawNeedle(painter)
def drawMarkings(self, painter):
painter.save()
painter.translate(self.width() / 2, self.height() / 2)
scale = min(
(self.width() - self._margins) / 120.0,
(self.height() - self._margins) / 60.0,
)
painter.scale(scale, scale)
font = QtGui.QFont(self.font())
font.setPixelSize(10)
metrics = QtGui.QFontMetricsF(font)
painter.setFont(font)
painter.setPen(self.palette().color(QtGui.QPalette.Shadow))
i = 0
while i < 360:
if i % 30 == 0 and (i < 150 or i > 210):
painter.drawLine(0, -40, 0, -50)
painter.drawText(
-metrics.width(self._pointText[i]) / 2.0, -52, self._pointText[i]
)
elif i < 135 or i > 225:
painter.drawLine(0, -45, 0, -50)
painter.rotate(15)
i += 15
painter.restore()
def drawNeedle(self, painter):
painter.save()
painter.translate(self.width() / 2, self.height() / 1.5)
painter.rotate(self._angle)
scale = min(
(self.width() - self._margins) / 120.0,
(self.height() - self._margins) / 120.0,
)
painter.scale(scale, scale)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(self.palette().brush(QtGui.QPalette.Shadow))
painter.drawPolygon(
QtGui.QPolygon(
[
QtCore.QPoint(-10, 0),
QtCore.QPoint(0, -45),
QtCore.QPoint(10, 0),
QtCore.QPoint(0, 5),
QtCore.QPoint(-10, 0),
]
)
)
painter.setBrush(self.palette().brush(QtGui.QPalette.Highlight))
painter.drawPolygon(
QtGui.QPolygon(
[
QtCore.QPoint(-5, -25),
QtCore.QPoint(0, -45),
QtCore.QPoint(5, -25),
QtCore.QPoint(0, -30),
QtCore.QPoint(-5, -25),
]
)
)
painter.restore()
def sizeHint(self):
return QtCore.QSize(150, 150)
def angle(self):
return self._angle
@QtCore.pyqtSlot(float)
def setAngle(self, angle):
if angle != self._angle:
self._angle = angle
self.angleChanged.emit(angle)
self.update()
angle = QtCore.pyqtProperty(float, angle, setAngle)
if __name__ == "__main__":
import sys
import asyncio
from asyncqt import QEventLoop
app = QtWidgets.QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
with loop:
w = Speedometer()
w.angle = 10
w.show()
loop.run_forever()
server.py
import asyncio
from PyQt5 import QtCore
class UDPserver(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(float, float, float, float)
def __init__(self, parent=None):
super().__init__(parent)
self._transport = None
self._counter_message = 0
@property
def transport(self):
return self._transport
def connection_made(self, transport):
self._transport = transport
def datagram_received(self, data, addr):
self._counter_message += 1
print("#Num of Mssg Received: {}".format(self._counter_message))
message = data.decode()
east_coord_str, north_coord_str, veh_speed_str, ag_yaw_str, *_ = message.split(
"/"
)
try:
east_coord = float(east_coord_str)
north_coord = float(north_coord_str)
veh_speed = float(veh_speed_str)
ag_yaw = float(ag_yaw_str)
self.dataChanged.emit(east_coord, north_coord, veh_speed, ag_yaw)
except ValueError as e:
print(e)
main.py
import sys
import asyncio
from PyQt5 import QtCore, QtWidgets
from asyncqt import QEventLoop
from speedometer import Speedometer
from server import UDPserver
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.spd = Speedometer()
self.spinBox = QtWidgets.QSpinBox()
self.spinBox.setRange(0, 359)
self.spinBox.valueChanged.connect(lambda value: self.spd.setAngle(value))
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.spd)
layout.addWidget(self.spinBox)
@QtCore.pyqtSlot(float, float, float, float)
def set_data(self, east_coord, north_coord, veh_speed, ag_yaw):
print(east_coord, north_coord, veh_speed, ag_yaw)
self.spd.setAngle(veh_speed)
async def create_server(loop):
return await loop.create_datagram_endpoint(
lambda: UDPserver(), local_addr=("127.0.0.1", 20002)
)
def main():
app = QtWidgets.QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
w = Widget()
w.resize(640, 480)
w.show()
with loop:
_, protocol = loop.run_until_complete(create_server(loop))
protocol.dataChanged.connect(w.set_data)
loop.run_forever()
if __name__ == "__main__":
main()
Upvotes: 7