diff --git a/.gitignore b/.gitignore index 280a6d7..b63ccab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .venv/* */_build/**/ */_static/**/ -*/_templates/**/ \ No newline at end of file +*/_templates/**/ +*.pyc \ No newline at end of file diff --git a/README.md b/README.md index 6ac6cc1..a0974f7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# NanoBot +# NanoNav Educational MicroPython robotics kit See documentation and start guides [here](https://bram-hub.github.io/NanoNav/) diff --git a/docs/source/bluetooth.rst b/docs/source/bluetooth.rst index ff6e091..503e9b5 100644 --- a/docs/source/bluetooth.rst +++ b/docs/source/bluetooth.rst @@ -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 ---------------------- @@ -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 @@ -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 `_ for advanced uses). \ No newline at end of file +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 `_. \ No newline at end of file diff --git a/docs/source/nanonav.py b/docs/source/nanonav.py index 54ce641..527bdf0 100644 --- a/docs/source/nanonav.py +++ b/docs/source/nanonav.py @@ -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 @@ -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 \ No newline at end of file diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 7ff6f1b..70dcb5f 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -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. diff --git a/nanonav.py b/nanonav.py index 94914cd..4ebc35a 100644 --- a/nanonav.py +++ b/nanonav.py @@ -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 @@ -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) @@ -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() @@ -51,27 +58,41 @@ 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): @@ -79,30 +100,21 @@ def send(self, value): 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'") \ No newline at end of file