move EV3 mailboxes to ev3messaging module

- add separate maibox classes
- drop bluetooth module since it is not considered public API
This commit is contained in:
David Lechner
2020-02-12 09:55:28 +01:00
committed by Laurens Valk
parent 7acbcbd874
commit b1382f02b0
8 changed files with 324 additions and 381 deletions
-27
View File
@@ -1,27 +0,0 @@
:mod:`bluetooth <pybricks.bluetooth>` -- Bluetooth
==================================================
.. automodule:: pybricks.bluetooth
:no-members:
.. currentmodule:: pybricks.bluetooth
EV3 Mailboxes
-------------
Pybricks MicroPython provides a mailbox implementation that is compatible with
the standard LEGO firmware. This can be used to communicate between multiple
EV3 bricks running Pybrick MicroPython, the standard LEGO firmware or a
combination of the two.
.. note:: See :doc:`ev3_mailboxes` for a more general overview.
.. autoclass:: EV3MailboxClient
.. autoclass:: EV3MailboxServer
.. autoclass:: EV3MailboxMixIn
.. autodata:: ALL_BRICKS
:annotation:
+3 -30
View File
@@ -48,36 +48,9 @@ program is that client program has connect block. Server is always running.
TODO: show equivalent blocks
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.get_logic`
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.get_numeric`
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.get_text`
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.send_logic`
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.send_numeric`
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.send_text`
.. rubric:: Sending objects as text
Simple Python objects, like dictionaries, can be encoded as text using the
builtin ``repr`` function and turned in to objects again using the builtin
``eval`` function.
Example::
# Server program
my_obj = { 'a': 1, 'b': 2 }
server.send_text(ALL_BRICKS, 'my_obj', repr(my_obj))
::
# Client program
client.wait_for_update('my_obj')
my_obj = eval(client.get_text('my_obj'))
.. warning:: In general, ``eval`` is considered a security risk because it
can execute arbitrary code! Never use ``eval`` with untrusted data, like
like data received from the Internet.
- :class:`pybricks.ev3messaging.LogicMailbox`
- :class:`pybricks.ev3messaging.NumericMailbox`
- :class:`pybricks.ev3messaging.TextMailbox`
.. rubric:: More than two bricks
+43
View File
@@ -0,0 +1,43 @@
:mod:`ev3messaging <pybricks.ev3messaging>` -- EV3 Messaging
============================================================
.. automodule:: pybricks.ev3messaging
:no-members:
.. currentmodule:: pybricks.ev3messaging
Mailboxes
---------
Mailboxes are used to send data to and from other EV3s.
.. autoclass:: LogicMailbox
:no-members:
.. automethod:: read
.. automethod:: send
.. automethod:: wait
.. automethod:: wait_new
.. autoclass:: NumericMailbox
:no-members:
.. automethod:: read
.. automethod:: send
.. automethod:: wait
.. automethod:: wait_new
.. autoclass:: TextMailbox
:no-members:
.. automethod:: read
.. automethod:: send
.. automethod:: wait
.. automethod:: wait_new
.. autoclass:: Mailbox
.. autoclass:: BluetoothMailboxServer
.. autoclass:: BluetoothMailboxClient
+1 -1
View File
@@ -29,7 +29,7 @@ Intro
tools
robotics
media
bluetooth
ev3messaging
.. toctree::
:maxdepth: 1
+1
View File
@@ -112,6 +112,7 @@ nitpick_ignore = [
('py:class', 'bool'),
('py:class', 'bytearray'),
('py:class', 'bytes'),
('py:class', 'callable'),
('py:class', 'dict'),
('py:class', 'float'),
('py:class', 'int'),
-322
View File
@@ -1,322 +0,0 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2020 David Lechner
"""This module provides classes for working with Bluetooth Classic.
.. availability::
:ev3dev-stretch:
.. versionadded:: 2.0.0
"""
from socketserver import BaseServer, ThreadingMixIn, StreamRequestHandler
try:
from socket import BDADDR_ANY
except ImportError:
BDADDR_ANY = '00:00:00:00:00:00'
__all__ = ['BDADDR_ANY', 'RFCOMMServer', 'ThreadingRFCOMMServer',
'StreamRequestHandler', 'ALL_BRICKS', 'EV3MailboxServer',
'EV3MailboxClient']
class RFCOMMServer(BaseServer):
"""Object that simplifies setting up an RFCOMM socket server.
This is based on the ``socketserver.SocketServer`` class in the Python
standard library.
.. availability::
:ev3dev-stretch:
.. versionadded:: 2.0.0
"""
class ThreadingRFCOMMServer(ThreadingMixIn, RFCOMMServer):
"""Version of :class:`RFCOMMServer` that handles connections in a new
thread.
.. availability::
:ev3dev-stretch:
.. versionadded:: version
.. versionadded:: 2.0.0
"""
ALL_BRICKS = None
"""Can be used in ``send`` methods to broadcast to call connected bricks.
.. availability::
:ev3dev-stretch:
.. versionadded:: 2.0.0
"""
EV3_RFCOMM_CHANNEL = 1
class EV3MailboxMixIn:
"""Methods shared by both :class:`EV3MailboxServer` and
:class:`EV3MailboxClient`
.. availability::
:ev3dev-stretch:
.. versionadded:: 2.0.0
"""
def get_raw_data(self, mbox):
"""Gets the current raw data from a mailbox.
Arguments:
mbox (str):
The name of the mailbox.
Returns:
bytes:
The current mailbox raw data or ``None`` if nothing has ever
been delivered to the mailbox.
"""
return b''
def get_packed_data(self, mbox, fmt):
"""Gets the current packed data from a mailbox.
Arguments:
mbox (str):
The name of the mailbox.
fmt (str):
``ustruct.unpack()`` format string used to decode the binary
data.
Returns:
tuple:
The result of ``ustruct.unpack()`` on the mailbox data.
Raises:
TypeError:
``fmt`` is not a string.
RuntimeError:
``mbox`` is empty.
"""
return ()
def get_logic(self, mbox):
"""Gets the current value of the mailbox as a boolean value.
This is compatible with the "logic" mailbox type in EV3-G.
Arguments:
mbox (str):
The name of the mailbox.
Returns:
bool:
The current value or ``None`` if the mailbox is empty.
"""
return False
def get_numeric(self, mbox):
"""Gets the current value of the mailbox as a floating point value.
This is compatible with the "numeric" mailbox type in EV3-G.
Arguments:
mbox (str):
The name of the mailbox.
Returns:
float:
The current value or ``None`` if the mailbox is empty.
"""
return 0.0
def get_text(self, mbox):
"""Gets the current value of the mailbox as a string value.
This is compatible with the "text" mailbox type in EV3-G.
Arguments:
mbox (str):
The name of the mailbox.
Returns:
str:
The current value or ``None`` if the mailbox is empty.
"""
return ""
def send_raw_data(self, brick, mbox, payload):
"""Sends a mailbox value using raw bytes data.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
brick (str):
The name or Bluetooth address of the brick or
:data:`ALL_BRICKS` to broadcast to all connected devices.
mbox (str):
The name of the mailbox.
payload (bytes):
A bytes-like object that will be sent to the mailbox.
"""
def send_packed_data(self, brick, mbox, fmt, *args):
"""Sends a mailbox value using packed values.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
brick (str):
The name or Bluetooth address of the brick or
:data:`ALL_BRICKS` to broadcast to all connected devices.
mbox (str):
The name of the mailbox.
fmt (str):
Format string compatible with ``ustruct.pack()``
*:
Arguments for ``ustruct.pack()``
"""
def send_logic(self, brick, mbox, value):
"""Sends a boolean mailbox value.
This is compatible with the "logic" mailbox type in EV3-G.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
brick (str):
The name or Bluetooth address of the brick or
:data:`ALL_BRICKS` to broadcast to all connected devices.
mbox (str):
The name of the mailbox.
value (bool):
The value that will be delivered to the mailbox.
"""
def send_numeric(self, brick, mbox, value):
"""Sends a float mailbox value.
This is compatible with the "numeric" mailbox type in EV3-G.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
brick (str):
The name or Bluetooth address of the brick or
:data:`ALL_BRICKS` to broadcast to all connected devices.
mbox (str):
The name of the mailbox.
value (bool):
The value that will be delivered to the mailbox.
"""
def send_text(self, brick, mbox, value):
"""Sends a string mailbox value.
This is compatible with the "text" mailbox type in EV3-G.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
brick (str):
The name or Bluetooth address of the brick or
:data:`ALL_BRICKS` to broadcast to all connected devices.
mbox (str):
The name of the mailbox.
value (bool):
The value that will be delivered to the mailbox.
"""
def wait_for_update(self, mbox):
"""Waits until ``mbox`` receives a value.
Arguments:
mbox (str):
The name of the mailbox.
"""
class EV3MailboxServer(EV3MailboxMixIn, ThreadingRFCOMMServer):
"""Object that represents an incoming Bluetooth connection from another
EV3.
The remote EV3 can either be running MicroPython or the standard EV3
firmare.
See :class:`EV3MailboxMixIn` for additional methods.
.. availability::
:ev3dev-stretch:
.. versionadded:: 2.0.0
"""
def __init__(self):
pass
def wait_for_connection(self, count=1):
"""Waits for a :class:`EV3MailboxClient` on a remote device to connect.
Arguments:
count (int):
The number of remote connections to wait for.
Raises:
OSError:
There was a problem establishing the connection.
"""
class EV3MailboxClient(EV3MailboxMixIn, ThreadingMixIn):
"""Object that represents an outgoing Bluetooth connection to another
EV3.
The remote EV3 can either be running MicroPython or the standard EV3
firmare.
See :class:`EV3MailboxMixIn` for additional methods.
.. availability::
:ev3dev-stretch:
.. versionadded:: 2.0.0
"""
def __init__(self, brick):
"""
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
brick (str):
The name or Bluetooth address of the remote EV3 to connect to.
"""
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
def connect(self):
"""Connects to an :class:`EV3MailboxServer` on another device.
The remote device must be paired and waiting for a connection. See
:meth:`EV3MailboxServer.wait_for_connection`.
Raises:
OSError:
There was a problem establishing the connection.
"""
def close(self):
"""Closes the connection."""
+275
View File
@@ -0,0 +1,275 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2020 David Lechner
"""
This module contains everything to do with EV3 bytecode messages.
.. availability::
:ev3dev-stretch:
.. versionadded:: 2.0.0
"""
class Mailbox:
def __init__(self, name, connection, encode=repr, decode=eval):
"""Object that represents a mailbox that contains encoded data.
See :class:`LogicMailbox`, :class:`NumericMailbox` and
:class:`TextMailbox` for mailboxes that are compatible with the
standard EV3 firmware and desktop programming software.
``encode`` is used in :meth:`send` and ``decode`` is used in
:meth:`read`. The defaults of ``repr`` and ``eval`` work with most
builtin types (like dict and list) but don't work for other objects
(like motors and sensors).
.. warning:: In general, ``eval`` is considered a security risk because
it can execute arbitrary code! Never use ``eval`` with untrusted
data, like like data received from the Internet.
Arguments:
name (str):
The name of this mailbox.
connection:
A connection object such as :class:`BluetoothMailboxClient`.
encode (callable):
Function that encodes a Python object to bytes.
decode (callable):
Function that creates a new Python object from bytes.
"""
def read(self):
"""Gets the current value of the mailbox.
Returns:
The current value or ``None`` if the mailbox is empty.
"""
return ''
def send(self, value, brick=None):
"""Sends a value to this mailbox on connected devices.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
value:
The value that will be delivered to the mailbox.
brick (str):
The name or Bluetooth address of the brick or ``None`` to
to broadcast to all connected devices.
Raises:
OSError:
There is a problem with the connection.
"""
def wait(self):
"""Waits for the mailbox to be updated by remote device."""
def wait_new(self):
"""Waits for a new value to be delivered to the mailbox that is not
equal to the current value in the mailbox.
Returns:
The new value.
"""
return object()
class LogicMailbox(Mailbox):
def __init__(self, name, connection):
"""
Object that represents a mailbox that contains a logic (true/false)
value.
This is compatible with the "logic" mailbox type in EV3-G.
Arguments:
name (str):
The name of this mailbox.
connection:
A connection object such as :class:`BluetoothMailboxClient`.
"""
def read(self):
"""Gets the current value of the mailbox as a boolean value.
Returns:
bool:
The current value or ``None`` if the mailbox is empty.
"""
return ''
def send(self, value, brick=None):
"""Sends a boolean value to this mailbox on connected devices.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
value (bool):
The value that will be delivered to the mailbox.
brick (str):
The name or Bluetooth address of the brick or ``None`` to
to broadcast to all connected devices.
Raises:
OSError:
There is a problem with the connection.
"""
class NumericMailbox(Mailbox):
def __init__(self, name, connection):
"""
Object that represents a mailbox that contains a number.
This is compatible with the "numeric" mailbox type in EV3-G.
Arguments:
name (str):
The name of this mailbox.
connection:
A connection object such as :class:`BluetoothMailboxClient`.
"""
def read(self):
"""Gets the current value of the mailbox as a numeric value.
Returns:
float:
The current value or ``None`` if the mailbox is empty.
"""
return ''
def send(self, value, brick=None):
"""Sends a numeric value to this mailbox on connected devices.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
value (float):
The value that will be delivered to the mailbox.
brick (str):
The name or Bluetooth address of the brick or ``None`` to
to broadcast to all connected devices.
Raises:
TypeError:
``value`` connot be converted to a floating point.
OSError:
There is a problem with the connection.
"""
class TextMailbox(Mailbox):
def __init__(self, name, connection):
"""
Object that represents a mailbox that contains text.
This is compatible with the "text" mailbox type in EV3-G.
Arguments:
name (str):
The name of this mailbox.
connection:
A connection object such as :class:`BluetoothMailboxClient`.
"""
def read(self):
"""Gets the current value of the mailbox as a string value.
Returns:
str:
The current value or ``None`` if the mailbox is empty.
"""
return ""
def send(self, value, brick=None):
"""Sends a string value to this mailbox on connected devices.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
value (str):
The value that will be delivered to the mailbox.
brick (str):
The name or Bluetooth address of the brick or ``None`` to
to broadcast to all connected devices.
Raises:
OSError:
There is a problem with the connection.
"""
class BluetoothMailboxServer:
"""Object that represents an incoming Bluetooth connection from another
EV3.
The remote EV3 can either be running MicroPython or the standard EV3
firmare.
"""
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
def wait_for_connection(self, count=1):
"""Waits for a :class:`BluetoothMailboxClient` on a remote device to
connect.
Arguments:
count (int):
The number of remote connections to wait for.
Raises:
OSError:
There was a problem establishing the connection.
"""
def close(self):
"""Closes all connections."""
class BluetoothMailboxClient:
"""Object that represents an outgoing Bluetooth connection to another
EV3.
The remote EV3 can either be running MicroPython or the standard EV3
firmare.
"""
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
def connect(self, brick):
"""Connects to an :class:`BluetoothMailboxServer` on another device.
The remote device must be paired and waiting for a connection. See
:meth:`BluetoothMailboxServer.wait_for_connection`.
.. todo:: Currently the Bluetooth address must be used instead of the
the brick name.
Arguments:
brick (str):
The name or Bluetooth address of the remote EV3 to connect to.
Raises:
OSError:
There was a problem establishing the connection.
"""
def close(self):
"""Closes all connections."""