Files
pybricks-projects/snippets/ev3/bluetooth_pc/pybricks/bluetooth.py
T
Laurens Valk d5ee42156d ev3/snippets/bluetooth_pc: StreamRequestHandler
This restores the previously deleted custom StreamRequestHandler.

In the prevous commits, more and more pieces from the standard socket library were used instead of the custom-made ones made for EV3. This was giving compatibility issues on some platforms (Windows/Mac), and differences with EV3 made it harder to debug.

It was previously dropped to make the MailboxHandler work. This commit reverts that change and modifies the MailboxHandler instead to make it work with the BluetoothSocket objects, which do not have rfile.read. Instead we use recv directly and rely on zero length to detect disconnected state.

It would be useful to eventually use only components from the socket server module and no incomplete custom ones, but this provides at least a working cross platform starting point to fall back to.
2020-05-16 20:13:40 +02:00

146 lines
3.8 KiB
Python

# SPDX-License-Identifier: MIT
# Copyright (C) 2020 The Pybricks Authors
"""
:class:`RFCOMMServer` can be used to communicate with other Bluetooth RFCOMM
devices that don't support the EV3 mailbox protocol.
It is based on the standard library ``socketserver`` module and attempts to
remain a strict subset of that implementation when it comes to low-level
implementation details.
"""
from bluetooth import BluetoothSocket, RFCOMM
from socketserver import ThreadingMixIn
BDADDR_ANY = ""
def str2ba(string, ba):
"""Convert string to Bluetooth address"""
for i, v in enumerate(string.split(':')):
ba.b[5-i] = int(v, 16)
def ba2str(ba):
"""Convert Bluetooth address to string"""
string = []
for b in ba.b:
string.append('{:02X}'.format(b))
string.reverse()
return ':'.join(string).upper()
class RFCOMMServer:
"""Object that simplifies setting up an RFCOMM socket server.
This is based on the ``socketserver.SocketServer`` class in the Python
standard library.
"""
request_queue_size = 1
def __init__(self, server_address, RequestHandlerClass):
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass
self.socket = BluetoothSocket(RFCOMM)
try:
self.socket.bind((server_address[0], server_address[1]))
# self.server_address = self.socket.getsockname()
self.socket.listen(self.request_queue_size)
except Exception:
self.server_close()
raise
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.server_close()
def handle_request(self):
try:
request, addr_data = self.socket.accept()
except OSError:
return
try:
self.process_request(request, addr_data)
except Exception:
request.close()
raise
def process_request(self, request, client_address):
self.finish_request(request, client_address)
request.close()
def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self)
def server_close(self):
self.socket.close()
class StreamRequestHandler:
"""Class that handles incoming requests.
This is based on ``socketserver.StreamRequestHandler`` from the Python
standard library.
"""
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
def setup(self):
self.wfile = self.request
self.rfile = self.request
def handle(self):
pass
def finish(self):
pass
class ThreadingRFCOMMServer(ThreadingMixIn, RFCOMMServer):
"""Version of :class:`RFCOMMServer` that handles connections in a new
thread.
"""
pass
class RFCOMMClient:
def __init__(self, client_address, RequestHandlerClass):
self.client_address = client_address
self.RequestHandlerClass = RequestHandlerClass
self.socket = BluetoothSocket(RFCOMM)
def handle_request(self):
self.socket.connect((self.client_address[0], self.client_address[1]))
try:
self.process_request(self.socket, self.client_address)
except Exception:
self.socket.close()
raise
def process_request(self, request, client_address):
self.finish_request(request, client_address)
request.close()
def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self)
def client_close(self):
self.socket.close()
class ThreadingRFCOMMClient(ThreadingMixIn, RFCOMMClient):
pass