Skip to content

Commit

Permalink
Add BLE on_connected and on_disconnected documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikUmble committed May 1, 2024
1 parent 8c4033a commit 6316e09
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 37 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.venv/*
*/_build/**/
*/_static/**/
*/_templates/**/
*/_templates/**/
*.pyc
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# NanoBot
# NanoNav
Educational MicroPython robotics kit
See documentation and start guides [here](https://bram-hub.github.io/NanoNav/)
17 changes: 12 additions & 5 deletions docs/source/bluetooth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ Quick Example
ble.send(43)
response = ble.read()
# wait until something changes, indicating a response
while response == 43:
response = ble.read()
print("Received: ", response)
Usage
-----

.. autoclass:: nanonav.BLE
:members:
:special-members: __init__,


.. note::
Just as a heads up, we've noticed that occasionally the value stored on the BLE characteristic gets corrupted. Wherever you call the `read` method, it's a good idea to verify that
the value is within the range you expect (and not ``None``), and if not, consider requesting the value again.

Connecting from Mobile
----------------------
Expand All @@ -36,7 +42,7 @@ If the NanoNav is waiting for a BLE connection, you will see it as one of the co

.. note::
On some versions of iOS, the BLE devices are automatically renamed, and NanoNav's connection may show up as "Arduino" or something else. If you find yourself in this
situation, it can be helpful to search for the service id instead. TODO: link to explanation for how this can be done.
situation, it can be helpful to search for the service id instead. TODO: add explanation for how this can be done.

.. image:: images/lightblue_devices_view.png
:width: 400
Expand All @@ -59,5 +65,6 @@ You can think of a BLE connection as a secret whiteboard that you and your frien
can look at (read) whatever is on it whenever you like, and can also change (write to) it whenever you like. Inside the LightBlue app, as shown in the
above picture, you can click the :blue:`Read Again` button as often as you would like, but the value will only
change when you (or NanoNav) writes to it. And you can send a number as often as you want in LightBlue, but NanoNav will not know unless you program it to
read the value periodically. (Actually, you can setup BLE interrupts for NanoNav to run code when something changes in the BLE connection. You will not need to do this,
but if interested, see `here <https://docs.micropython.org/en/latest/library/bluetooth.html>`_ for advanced uses).
read the value periodically. (Actually, you can setup BLE interrupts for NanoNav to run code when something changes in the BLE connection, similar to the :py:meth:`~nanonav.BLE.on_connected` and :py:meth:`~nanonav.BLE.on_disconnected`, but we think
you'll have an easier time getting your code to work as expected by avoiding that kind of programming for now).
If interested in learning about other bluetooth capabilities beyond the scope of NanoNav, see `here <https://docs.micropython.org/en/latest/library/bluetooth.html>`_.
30 changes: 22 additions & 8 deletions docs/source/nanonav.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

class BLE:
"""
A helpful wraper around the BLE service functions needed for the Wumpus World project
A helpful wraper around the MicroPython BLE service functions.
:param ble: The bluetooth object to use for BLE communication (leave as default unless you know why it should be different).
:type ble: bluetooth.BLE
:param name: The name of the device to advertise (default: "NANO RP2040").
:type name: str
"""
def __init__(self, ble=bluetooth.BLE(), name="NANO RP2040"):
pass
Expand All @@ -26,20 +31,29 @@ def send(self, value):
Send value to the bluetooth characteristic.
:param value: The value to send.
:type value: bytes, int, str
:raise ValueError: If the value is not bytes, int, or str.
:type value: int, bytes, or str
:raise ValueError: If the value is not int, bytes, or str.
"""
pass

def read(self, as_type="bytes"):
def read(self):
"""
Return the current value of the bluetooth characteristic, or None if an error occurred.
:param as_type: The type to return the value as. Must be one of 'bytes', 'str', or 'int'.
:type as_type: str
:return: The value of the characteristic.
:rtype: bytes, str, int, None
:raise ValueError: If as_type is not 'bytes', 'str', or 'int'.
:rtype: int, None
"""
pass

def on_connected(self):
"""
You may specify this method to be called once the BLE connection is established.
"""
pass

def on_disconnected(self):
"""
You may specify this method to be called once the BLE connection is lost.
"""
pass
2 changes: 1 addition & 1 deletion docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ MicroPython
-----------

In general, MicroPython is very similar to regular Python, but there are some difference we would like to point you to before you begin. MicroPython has its own library of
packages, which are different from the PyPi packages you may be used to (if you ever ```pip install``` anything). We provide helper functions for the ways we think you'll need to
packages, which are different from the PyPi packages you may be used to (if you ever ``pip install`` anything). We provide helper functions for the ways we think you'll need to
interact with the Arduino, Bluetooth, and peripherals, and just about anything you can do in Python 3.11 can also be done in MicroPython, but note that you will not have access to the full standard Python library. For instance, you can import `time` since this has been added to
MicroPython's library, but you cannot import `Queue` or other familiar packages. If ever in doubt about whether MicroPython supports a particular package, simply google "MicroPython [package name]",
and you will likely find the information you need.
Expand Down
54 changes: 33 additions & 21 deletions nanonav.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
#
#
#
# TODO: Strip comments and docstrings once docs/source/nanonav.py skeleton is complete
# (it's ok to leave them in for now while we are finalizing the documentation)
#


from ble_advertising import advertising_payload
import bluetooth
Expand All @@ -23,8 +30,8 @@ class BLE:
def __init__(self, ble=bluetooth.BLE(), name="NANO RP2040"):
# Setup bluetooth low energy communication service
_SERVICE_UUID = bluetooth.UUID(0x1523) # unique service id for the communication
_NanoBot_CHAR_UUID = (bluetooth.UUID(0x1525), _FLAG_WRITE | _FLAG_READ) # characteristic
_NanoBot_SERVICE = (_SERVICE_UUID, (_NanoBot_CHAR_UUID,),) # service to provide the characteristic
_NanoNav_CHAR_UUID = (bluetooth.UUID(0x1525), _FLAG_WRITE | _FLAG_READ) # characteristic
_NanoNav_SERVICE = (_SERVICE_UUID, (_NanoNav_CHAR_UUID,),) # service to provide the characteristic

self._ble = ble
self._ble.active(True)
Expand All @@ -35,7 +42,7 @@ def __init__(self, ble=bluetooth.BLE(), name="NANO RP2040"):
io=_IO_CAPABILITY_DISPLAY_ONLY
)
self._ble.irq(self._irq)
((self._handle,),) = self._ble.gatts_register_services((_NanoBot_SERVICE,))
((self._handle,),) = self._ble.gatts_register_services((_NanoNav_SERVICE,))
self._connections = set()
self._payload = advertising_payload(name=name, services=[_SERVICE_UUID])
self._advertise()
Expand All @@ -51,58 +58,63 @@ def _irq(self, event, data):
conn_handle, addr_type, addr = data
self._connections.add(conn_handle)

self.on_connected()

elif event == _IRQ_CENTRAL_DISCONNECT:
# handle disconnect
conn_handle, _, _ = data
self._connections.remove(conn_handle)
self._advertise()

self.on_disconnected()

elif event == _IRQ_GATTS_WRITE:
conn_handle, value_handle = data
if conn_handle in self._connections:
# Value has been written to the characteristic
self.value = self._ble.gatts_read(value_handle)

def on_connected(self):
"""
You may specify this method to be called once the BLE connection is established.
"""
pass

def on_disconnected(self):
"""
You may specify this method to be called once the BLE connection is lost.
"""
pass

def send(self, value):
"""
Send value to the bluetooth characteristic.
:param value: The value to send.
:type value: bytes, int, str
:raise ValueError: If the value is not bytes, int, or str.
:type value: int, bytes, or str
:raise ValueError: If the value is not int, bytes, or str.
"""
if not isinstance(value, bytes):
if isinstance(value, int):
value = value.to_bytes(1, "big")
elif isinstance(value, str):
value = value.encode('utf-8')
else:
raise ValueError("send value should be type bytes, int, or string")
raise ValueError("send value should be type int, bytes, or string")
self.value = value
self._ble.gatts_write(self._handle, value)

def read(self, as_type="bytes"):
def read(self):
"""
Return the current value of the bluetooth characteristic, or None if an error occurred.
:param as_type: The type to return the value as. Must be one of 'bytes', 'str', or 'int'.
:type as_type: str
:return: The value of the characteristic.
:rtype: bytes, str, int, None
:raise ValueError: If as_type is not 'bytes', 'str', or 'int'.
:rtype: int, None
"""
value = self.value # try using the last value written to characteristic
#use the last value written to characteristic
value = self.value
try:
if as_type == "bytes":
return value
elif as_type == "str":
return value.decode("utf-8")
elif as_type == "int":
print(f'read {value}')
return int.from_bytes(value, "big")
return int.from_bytes(value, "big")
except Exception as e:
return None

raise ValueError("as_type must be one of 'bytes', 'str', or 'int'")

0 comments on commit 6316e09

Please sign in to comment.