Can pygame events be handled in select.select input list? - python

The documentation for python's select.select says:
Note that on Windows, it only works for sockets; on other operating
systems, it also works for other file types (in particular, on Unix,
it works on pipes).
My group is developing a simplistic multiplayer game using pygame and sockets. (We are not using Twisted or zeromq or any similar libraries; this being the only constraint).
Now, for the game design; we want the player to send data to the server when a key event occurs in the pygame screen. The client/player side's socket will otherwise be hooked to server and listen for changes occurring on other players' side. For this task, I'd need the pygame and socket to work parallely. I was recommended to use select module from several users on #python.
Can I do something like:
inp = [self.sock, pygame.event.get]
out = [self.server]
i, o, x = select.select( inp, out, [] )
If not, what should be the way to go?

You could use threads for this task. Is it necessary to process server messages and pygame events in series (not concurrently)? If so, you could do this:
class SocketListener(threading.Thread):
def __init__(self, sock, queue):
threading.Thread.__init__(self)
self.daemon = True
self.socket = sock
self.queue = queue
def run(self):
while True:
msg = self.socket.recv()
self.queue.put(msg)
class PygameHandler(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
self.daemon = True
def run(self):
while True:
self.queue.put(pygame.event.wait())
queue = Queue.Queue()
PygameHandler(queue).start()
SocketListener(queue).start()
while True:
event = queue.get()
"""Process the event()"""
If not, you could process the events inside the run methods of the PygameHandler and SocketListener classes.

Related

Python async events and serial async?

I have code (python class, I'll call it Arduino) that writes control packets to a serial port (using serial_asyncio), and the serial port replies with confirmation packets. However the remote device also sends event packets to the python side randomly. I want the class to provide the decoded packets to the class that instantiates my class (I'll call it Controller). I am confused on how to handle this.
My first thought is to provide callback to the Arduino class:
class Controller:
def __init__(self):
self.arduino = Arduino("/dex/ttyS0", self._serialPacketCallback)
def _serialPacketCallback(self, obj: dict):
pass # handle spontaneous event packet from remote
But this does not seem very async-y. What is the asyncio way to do this? I think this would look like:
class Controller:
def __init__(self):
self.arduino = Arduino("/dex/ttyS0")
async readEventPacket(self):
pass
#somewhere, not sure where, or how to start it:
async def _handleEvents(self)
while True:
event = await self._readEventPacket()
async def start(self):
await self.arduino.start()
await asyncio.wait([self._handleEvents()])
if __name__ == '__main__':
controller = Controller()
loop = asyncio.get_event_loop()
loop.create_task(controller.start())
loop.run_forever()
I've looked around and I've seen suggestions of using callbacks, multi-processing pipes, additional event loops, and I am sure they work, but I'm not sure what the proper approach is. For me, I don't want to start any additional event loops or threads, leading me to think the callback is best, but that is not very async, and I would like to know how to do this as async, without additional event loops or callbacks.
An additional concern that I want to articulate is that I'd like this as loosely coupled as the Arduino class will be used in other controllers.
Side note: I am not sure when in Python a new event loop is required to be created?
Another question: how does the Arduino class generate an event and have Controller pick it up in await self._readEventPacket()?
The nice thing about asyncio is that you can always convert a callback-based interface to a coroutine-based one, and vice versa.
Let's assume your Arduino class implements a callback-based interface, like this (untested):
class Arduino:
def __init__(self, device, callback):
self._device = device
self._callback = callback
async def start(self):
reader, writer = await serial_asyncio.connect(url=self._device, baudrate=115200)
while True:
data = await reader.read(1024)
self._callback(data)
You can convert that interface into a coroutine-based one by using a queue:
def callback_to_coro():
# Return two functions, one that can be passed as callback to
# code that expects it, and the other a coroutine that can be
# awaited to get the values the callback was invoked with.
queue = asyncio.Queue()
return queue.put_nowait, queue.get
With that code you can implement Controller.read_event_packet like this:
class Controller:
def __init__(self):
callback, wait = callback_to_coro()
self.arduino = Arduino("/dex/ttyS0", callback)
self.read_event_packet = wait

Audio chunk distribution issue in Python based multi-client voice chat with 2 simultaneous speakers

I'm working on a Python based multi-client voice chat application (learning project). While voice transfer between 2 clients works like a charm and the server is also able to handle multiple connections, problems start as soon as 2 persons talk simultaneously. Whenever a client sends audio data (e.g. "AAAA"), while another client is sending "BBBB" the third clients receives something like "ABABABAB".
I've already tried to spawn multiple threads for each user and let a Mutex take control over the clients[] array (the latter one was either implemented wrong or a stupid idea since it doesn't change anything)
I've also included RMS (only send data while somebody is speaking) in order to stop the constant data stream from every connected client, which took a bit of heat from the server and helped a little bit without solving the real problem.
from socket import *
from threading import Thread, Lock
class entry_thread (Thread):
def __init__(self, host, port):
Thread.__init__(self)
adress_voice = (host, port)
self.voice_socket = socket(AF_INET, SOCK_STREAM)
self.voice_socket.bind(adress_voice)
self.voice_socket.listen(10)
def run(self):
while True:
sock_v, data_v = self.voice_socket.accept()
thread = handle_client_thread(sock_v, data_v)
thread.start()
class handle_client_thread (Thread):
def __init__(self, vsock, vdata):
global clients
Thread.__init__(self)
self.chunk = 1024
self.vsock = vsock
self.vdata = vdata
self.name = self.vsock.recv(1024).decode()
clients[self.name] = [self.vdata, self.vsock]
def run(self):
global clients
while True:
try:
audio_data = self.vsock.recv(self.chunk)
for x, client in clients.items():
if thread_safe:
mutex.acquire()
try:
if (client[0][1] != self.vdata[1]): #if receipient
# != sender
client[1].send(audio_data)
finally:
if thread_safe:
mutex.release()
except:
break
mutex = Lock()
thread_safe = False
clients = {}
server = entry_thread('', 20003)
server.start()
server.join()
Solved the issue by putting the whole for-loop into the mutex, while giving each user an individual audio channel within the client.

python-xbee cannot read frame in thread

I am using one XBee S2 as coordinator (API mode), 3 XBee S2 as routers (AT mode). The routers are connected to Naze32 board (using MSP).
On the computer side, I have a GUI using wxpython to send out command to request data.
The GUI will send out command to XBee (Coordinator) to request data from the routers every second.
I am using python-xbee library to do the send and receive frame job on computer side. Once new data received, it will notify the GUI to update some labels with the new data.
Currently I am able to send and receive frames outside a thread, but once I move the send and receive functions to a thread, it will never be able to read a frame any more. As I don't want to let the serial stop the GUI or make it not responding. Another thing is if I close the thread, then start new thread with xbee again, it will not work any more.
The communication is controlled by a button on the GUI; once "Turn on" clicked, the "self._serialOn" will set to True, then create new thread; once "Turn off" clicked, "self._serialOn" will set to False and thread is stopped.
How can I fix this problem?
Thanks in advance.
class DataExchange(object):
def __init__(self):
self._observers = []
self._addressList = [['\x00\x13\xA2\x00\x40\xC1\x43\x0F', '\xFF\xFE'],[],[]]
self._serialPort = ''
self._serialOn = False
self.workerSerial = None
# serial switch
def get_serialOn(self):
return self._serialOn
def set_serialOn(self, value):
self._serialOn = value
print(self._serialOn)
if self.serialOn == True:
EVT_ID_VALUE = wx.NewId()
self.workerSerial = WorkerSerialThread(self, EVT_ID_VALUE, self.serialPort, self.addressList)
self.workerSerial.daemon = True
self.workerSerial.start()
elif self.serialOn == False:
self.workerSerial.stop()
del(self.workerSerial)
self.workerSerial = None
serialOn = property(get_serialOn, set_serialOn)
class WorkerSerialThread(threading.Thread):
def __init__(self, notify_window, id, port, addresslist):
threading.Thread.__init__(self)
self.id = id
self.serialPort = port
self.addressList = addresslist
self.counter = 0
self._notify_window = notify_window
self.abort = False
self.sch = SerialCommunication(self.serialPort, self.addressList)
try:
self.sch.PreLoadInfo()
except:
print('failed')
def run(self):
while not self.abort:
self.counter += 1
print('Serial Working on '+str(self.id))
self.sch.RegularLoadInfo()
#wx.PostEvent(self._notify_window, DataEvent(self.counter, self.id))
time.sleep(1)
def stop(self):
self.sch.board.stop()
self.abort = True
This question was finally solved with multiprocessing rather than threading of python.
In the manual of python-xbee, it mentioned that "... Make sure that updates to external state are thread-safe...". Also in the source code, threading was used.
So I guess in this case threading will cause problem.
Anyway, using multiprocessing it finally works.

Different behavior in run and start in Python multiprocessing

I am trying to start multiple processes in a Python program, using multiprocessing.Queue to share data between them.
My code is shown as follows, TestClass is the process that receives packets from a zmq socket, and feeds them into the queue. There is another process(I took it out from the code) keeps fetching messages from the queue. I also have a script running to publish messages to this zmq channel.
from multiprocessing import Process, Queue
import zmq
import time
class TestClass(Process):
def __init__(self, queue):
super(TestClass, self).__init__()
# Setting up connections
self.context = zmq.Context()
self.socket = self.context.socket(zmq.SUB)
self.socket.connect("tcp://192.168.0.6:8577")
self.socket.setsockopt(zmq.SUBSCRIBE, b'')
self.queue = queue
def run(self):
while True:
msg = self.socket.recv()
self.queue.put(msg)
queue = Queue()
c = TestClass(queue)
c.run()
# Do something else
If I use c.run() to start the process, it runs fine, but it is not started as a Process because it blocks the following statement.
Then I switched to c.start() to start the process, but it was stuck at the line socket.recv() and cannot get any incoming messages. Can anybody please explain this and suggest a good solution? Thanks
The issue is that you're creating the zmq socket in the parent process, but then trying to use it in the child. Something in the forking process is breaking the socket, so it's not working when you try using it. You can fix it by simply creating the socket in the child, rather than the parent. This has no negative side effects, since you're not trying to use the socket in the parent to begin with.
from multiprocessing import Process, Queue
import zmq
import time
class TestClass(Process):
def __init__(self, queue):
super(TestClass, self).__init__()
self.queue = queue
def run(self):
# Setting up connections
self.context = zmq.Context()
self.socket = self.context.socket(zmq.SUB)
self.socket.connect("tcp://192.168.0.6:8577")
self.socket.setsockopt(zmq.SUBSCRIBE, b'')
while True:
msg = self.socket.recv()
self.queue.put(msg)
if __name__ == "__main__":
queue = Queue()
c = TestClass(queue)
c.start() # Don't use run()
# Do something else

Thread doesn't close on exiting tk window

I have an application written in Python, using Tkinter. One of the features allows a serial port to be opened, after which any messages received over the serial port are displayed in a text window. This works fine. The problem comes when I close the window, which doesn't kill the thread monitoring the serial port. It then has to be killed manually (alternatively, unplugging the USB-serial cable causes an exception which kills the process).
I assume I'm missing something simple here, but I would have thought closing the application would close all associated threads. I can't seem to find anything about this in the documentation, but I'm probably looking in the wrong place?
Code for the serial thread in case it's relevant:
class SerialThread(threading.Thread):
def __init__(self, queue, sp):
threading.Thread.__init__(self)
self.queue = queue
self.ser_handle = sp;
def run(self):
while True:
if self.ser_handle.inWaiting():
text = self.ser_handle.readline(self.ser_handle.inWaiting())
self.queue.put(text)
time.sleep(0.2)
You must have a way to ask the thread for stop, you can accomplish that using: threading.Event.
class SerialThread(threading.Thread):
def __init__(self, queue, sp):
threading.Thread.__init__(self)
self.queue = queue
self.event = threading.Event() # An event object.
self.ser_handle = sp;
def stop(self):
self.event.set()
def run(self):
while not self.event.isSet():
if self.ser_handle.inWaiting():
text = self.ser_handle.readline(self.ser_handle.inWaiting())
self.queue.put(text)
time.sleep(0.2)
Then in the on close event of your window, call your_thread.stop()

Categories

Resources