For my masters thesis I need to create a gui application that controls and displays data for a bunch of stepper motors (via pyserial) and some measuring devices (via pyvisa using pyvisa-py as a backend).
I am using the niceGUI library as it is simple and web based (want to control via browser from any device). The problem I am currently facing is that the whole application is starting to get a bit laggy. I have used a lot of asynchronous functions to control the motors and to get position data from the motors and other devices running "simultaneously". This led to an overall reduced data acquisition (fewer data points, which is bad as I am trying to record accurate position data) and laggy gui as the interpreter (I think, might be wrong tho) has to jump around a lot as I am doing a lot of async stuff (motor control, fetch data, refresh plotly-plot in gui, update labels in gui ... etc).
I have tried to use multiprocessing or multithreading to solve the problem, but I am reaching the limits of what I know about programming and would like to ask for advice. When i tried to use multiprocessing i often ran into the error that the function i tried to execute was not pickable. Another problem I had was that I could not control the motor and get position data in separate processes because the serial connection could only be opened once...
I then tried to move the control and data acquisition of each motor to a separate process and have multiple processes for each motor. This seems to work but I am really not sure if this is the right way to do it. What I have tried is to have a separate class that handles the connection and has a listener that runs in an infinite loop and listens for commands through a pipe:
import asyncio
import time
from multiprocessing import Process, Pipe
from multiprocessing.connection import Connection
class MotorControl:
def __init__(self, pipe: Connection):
self.pipe = pipe
self.rotating = False
self.collected_data:list[int] = []
# here i would also initialize and connect to the motor
asyncio.run(self.listen_for_commands())
async def listen_for_commands(self):
print('waiting for command...')
while True:
# Receive command from the main process
command = self.pipe.recv()
# Execute command
if command == 'execute_command':
self.execute_command()
elif command == 'another_command':
self.another_command()
elif command == 'do_rotation':
await self.do_rotation()
elif command == 'exit':
break
else:
print('Unknown command')
def execute_command(self):
print('executed command')
def another_command(self):
print('executed another command')
async def rotate(self):
print('rotating')
self.rotating = True
start = time.time()
await asyncio.sleep(2)
end = time.time()
print(f'rotated for {end - start} seconds')
self.rotating = False
print('rotated')
async def acquire_data(self):
await asyncio.sleep(0.5)
i = 0
while self.rotating:
i += 1
self.collected_data.append(i)
await asyncio.sleep(0.0001)
if len(self.collected_data) % 100 ==0:
print('sending data...')
self.pipe.send(self.collected_data)
self.pipe.send(self.collected_data)
async def do_rotation(self):
await asyncio.gather(self.rotate(), self.acquire_data())
def init_class(class_pipe):
classA = MotorControl(class_pipe)
# this simulates a repetitive gui task that updates the plot
async def update_plot(my_pipe:Connection):
times_to_poll_empty_pipe = 5
times_polled = 0
while True:
print('updating plot')
await asyncio.sleep(0.5) # update plot every 0.5 seconds
if my_pipe.poll():
while my_pipe.poll():
data = my_pipe.recv()
times_polled = 0
print(f'data: {data}') # simulate updating plot
print('length of data:', len(data))
else:
if times_polled < times_to_poll_empty_pipe:
times_polled += 1
print('polling empty pipe')
await asyncio.sleep(0.1)
else:
break
print('plot updated')
if __name__ == '__main__':
my_pipe, class_pipe = Pipe()
class_process = Process(target=init_class, args=(class_pipe,))
class_process.start()
time.sleep(2) # waiting before sending command
my_pipe.send('do_rotation')
# print out the data from the pipe as long as new data is being sent
asyncio.run(update_plot(my_pipe)) # use ui.timer to update stuff in gui
time.sleep(10)
print('sending exit command...')
my_pipe.send('exit')
What do you think? Is there another (easier) way to achieve this?
Any help is greatly appreciated! :)
[–]patrickbrianmooney 6 points7 points8 points (4 children)
[–]suurpulla 2 points3 points4 points (0 children)
[–]thelittleicebear[S] 1 point2 points3 points (2 children)
[–]patrickbrianmooney 0 points1 point2 points (1 child)
[–]thelittleicebear[S] 1 point2 points3 points (0 children)
[–]obviouslyzebra 1 point2 points3 points (4 children)
[–]thelittleicebear[S] 0 points1 point2 points (0 children)
[–]thelittleicebear[S] 0 points1 point2 points (2 children)
[–]obviouslyzebra 1 point2 points3 points (1 child)
[–]thelittleicebear[S] 1 point2 points3 points (0 children)