Skip to content

Commit

Permalink
examples/ev3/bluetooth: remove 3rd party dependency
Browse files Browse the repository at this point in the history
Since Python 3.10, RFCOMM sockets are available in Python on Windows
so we no longer need 3rd party code.

Fixes: pybricks/support#902
  • Loading branch information
dlech committed Jan 6, 2023
1 parent 8c673ab commit eea8ff0
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 69 deletions.
28 changes: 28 additions & 0 deletions examples/ev3/bluetooth_pc/pcserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
from pybricks.messaging import BluetoothMailboxServer, TextMailbox

# This demo makes your PC talk to an EV3 over Bluetooth.
#
# This is identical to the EV3 server example in ../bluetooth_server
#
# The only difference is that it runs in Python3 on your computer, thanks to
# the Python3 implementation of the messaging module that is included here.
# As far as the EV3 is concerned, it thinks it just talks to an EV3 client.
#
# So, the EV3 client example needs no further modifications. The connection
# procedure is also the same as documented in the messaging module docs:
# https://docs.pybricks.com/en/latest/messaging.html

server = BluetoothMailboxServer()
mbox = TextMailbox("greeting", server)

# The server must be started before the client!
print("waiting for connection...")
server.wait_for_connection()
print("connected!")

# In this program, the server waits for the client to send the first message
# and then sends a reply.
mbox.wait()
print(mbox.read())
mbox.send("hello to you!")
72 changes: 15 additions & 57 deletions examples/ev3/bluetooth_pc/pybricks/bluetooth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2020 The Pybricks Authors
# Copyright (C) 2020,2023 The Pybricks Authors

"""
:class:`RFCOMMServer` can be used to communicate with other Bluetooth RFCOMM
Expand All @@ -10,29 +10,13 @@
implementation details.
"""

from bluetooth import BluetoothSocket, RFCOMM
from socket import socket, AF_BLUETOOTH, BTPROTO_RFCOMM, SOCK_STREAM
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.
"""
Object that simplifies setting up an RFCOMM socket server.
This is based on the ``socketserver.SocketServer`` class in the Python
standard library.
Expand All @@ -44,10 +28,10 @@ def __init__(self, server_address, RequestHandlerClass):
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass

self.socket = BluetoothSocket(RFCOMM)
self.socket = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)

try:
self.socket.bind((server_address[0], server_address[1]))
self.socket.bind(server_address)
# self.server_address = self.socket.getsockname()
self.socket.listen(self.request_queue_size)
except Exception:
Expand Down Expand Up @@ -83,50 +67,21 @@ 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
Version of :class:`RFCOMMServer` that handles connections in a new thread.
"""
daemon_threads = True


class RFCOMMClient:
def __init__(self, client_address, RequestHandlerClass):
self.client_address = client_address
self.RequestHandlerClass = RequestHandlerClass
self.socket = BluetoothSocket(RFCOMM)
self.socket = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)

def handle_request(self):
self.socket.connect((self.client_address[0], self.client_address[1]))
self.socket.connect(self.client_address)
try:
self.process_request(self.socket, self.client_address)
except Exception:
Expand All @@ -145,4 +100,7 @@ def client_close(self):


class ThreadingRFCOMMClient(ThreadingMixIn, RFCOMMClient):
pass
"""
Version of :class:`RFCOMMClient` that handles connections in a new thread.
"""
daemon_threads = True
21 changes: 9 additions & 12 deletions examples/ev3/bluetooth_pc/pybricks/messaging.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2020 The Pybricks Authors
# Copyright (C) 2020,2023 The Pybricks Authors

from _thread import allocate_lock
from errno import ECONNRESET
from struct import pack, unpack
from socket import BDADDR_ANY
from socketserver import StreamRequestHandler
from threading import Lock

from .bluetooth import (
BDADDR_ANY,
ThreadingRFCOMMServer,
ThreadingRFCOMMClient,
StreamRequestHandler,
)
from .bluetooth import ThreadingRFCOMMServer, ThreadingRFCOMMClient


def resolve(brick):
Expand Down Expand Up @@ -151,7 +148,7 @@ def handle(self):
self.server._clients[self.client_address[0]] = self.request
while True:
try:
buf = self.rfile.recv(2)
buf = self.rfile.read(2)
if len(buf) == 0:
break
except OSError as ex:
Expand All @@ -160,7 +157,7 @@ def handle(self):
break
raise
(size,) = unpack("<H", buf)
buf = self.rfile.recv(size)
buf = self.rfile.read(size)
msg_count, cmd_type, cmd, name_size = unpack("<HBBB", buf[0:5])
if cmd_type != SYSTEM_COMMAND_NO_REPLY:
raise ValueError("Bad message type")
Expand All @@ -180,7 +177,7 @@ def handle(self):
class MailboxHandlerMixIn:
def __init__(self):
# protects against concurrent access of other attributes
self._lock = allocate_lock()
self._lock = Lock()
# map of mailbox name to raw data
self._mailboxes = {}
# map of device name/address to object with send() method
Expand Down Expand Up @@ -247,7 +244,7 @@ def send_to_mailbox(self, brick, mbox, payload):

def wait_for_mailbox_update(self, mbox):
"""Waits until ``mbox`` receives a value."""
lock = allocate_lock()
lock = Lock()
lock.acquire()
with self._lock:
self._updates[mbox] = lock
Expand Down

0 comments on commit eea8ff0

Please sign in to comment.