Populating our documentation

Now that we have setup everything, it is time we started populating our documentation. First of all, lets have a look at the index.rst file that was auto-generated during the Sphinx Quickstart step:

.. simpleble documentation master file, created by
sphinx-quickstart on Sat Mar 10 15:05:19 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

Welcome to simpleble's documentation!
=====================================

.. toctree::
:maxdepth: 2
:caption: Contents:



Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

A thorough syntax guide for Restructured Text (reST) and how it is used within Sphinx can be found here. In short, the ===== underline is used to denote page titles (in this case we have two, since it is our index, but it is good practice to have one title per .rst file) and the .. toctree:: directive is used to generate the directory tree (i.e. the Contents) of our documentation, which for now is empty. Finally, the :ref: directives are used to refer to the genindex.html , modindex.html and search.html pages, which are auto-generated by sphinx when we build our documentation.

Building our documentation

In general, when we want to build our (html) documentation, we do so by calling the following command in our Documentation root directory:

youruser@yourpc:~yourWorkspacePath/simpleble-master/docs$ make html

Running the above command will process any Sphinx source files contained within the simpleble-master/docs/source (i.e. our Sphinx source) directory and will proceed to generate an html folder under the simpleble-master/docs/build (i.e. our Sphinx build) directory, which contains all the relevant files (e.g. html, css, js etc.) used by a webserver to host our documentation in the form of a website.

It is generally good practice to run a make clean command before a make html to ensure that our html output is generated from scratch. In some cases, changes to the toctree in the index.rst file might not result in changes to the navigation sidebar of all pages of your documentation. If this happens, then you should run make clean followed by make html.

However, if we were to build our documentation at this point, and we viewed the generated simpleble-master/docs/build/html/index.html file in a browser, we would notice that it is pretty much empty. This is because we have not added anything to it since it was generated. This is what we’ll do next.

Generating documentation from docstrings

Now let’s see how we can auto-generate documentation from the docstrings in our Python source files. The sphinx-apidoc command can be used to auto-generate some .rst files for our Python module. This can be done as follows:

youruser@yourpc:~yourWorkspacePath/simpleble-master/docs$ sphinx-apidoc -o ./source ../simpleble
Creating file ./source/simpleble.rst.
Creating file ./source/modules.rst.

The above command generates two files: simpleble.rst and modules.rst in our Sphinx source root directory. The modules.rst file is usefull when more than one modules (.py files) are included in our Python source directory, but since we only have one module and to keep thing simple, we can ignore it for the purposes of this tutorial. Let’s have a look at the contents of simpleble.rst:

simpleble module
================

.. automodule:: simpleble
    :members:
    :undoc-members:
    :show-inheritance:

Notice that the file does not explicitly contain the generated documentation. Instead, the .. automodule:: directive is called, with the parameter simpleble (i.e. our module) and the directive options :members:, :undoc-members: and :show-inheritance:. This is sufficient for Sphinx, or more specifically autodoc, to generate a simpleble.html file, containing documentation generated from our docstrings, when the make html command is called. Check the links for more information on autodoc and the Python domain directives.

Modifying the files generated by sphinx-apidoc

Say, for example, that we wish to change the page title of our module documentation page, as well as split our documentation such as to have a separate sub-title for each of our classes. This can be done in our example by modifying the simpleble.rst file, as follows:

Documentation
=============

The ``SimpleBleClient`` class
*****************************
.. autoclass:: simpleble.SimpleBleClient
    :members:
    :undoc-members:
    :show-inheritance:


The ``SimpleBleDevice`` class
*****************************
.. autoclass:: simpleble.SimpleBleDevice
    :members:
    :undoc-members:
    :show-inheritance:


The ``SimpleBleScanDelegate`` class
***********************************
.. autoclass:: simpleble.SimpleBleScanDelegate
    :members:
    :undoc-members:
    :show-inheritance:

The ``SimpleBleNotificationDelegate`` class
*******************************************
.. autoclass:: simpleble.SimpleBleNotificationDelegate
    :members:
    :undoc-members:
    :show-inheritance:

Notice that we have used the .. autoclass:: directive instead of .. automodule::, which in essence instructs autodoc to only generate the documentation for the class identified by the classname specified after it. Note, however, that we must prefix the names of our classes with the name of the module. This will ensure that there are not name conflicts, especially when creating documentation for a number of modules.

Adding pages to our documentation

Even though we have generated the simpleble.rst file, in order to add it to our documentation we still need to reference it within the toctree specified in our index.rst file.

While we are here, let’s also create and add an “Introduction” and “Examples” page to our documentation. We start by creating a intro.rst file in our Sphinx source root (the same directory as the index.rst and simpleble.rts files), with the following content:

Introduction
============

``simpleble`` is a high-level OO Python package which aims to provide an easy and intuitive way of interacting with nearby Bluetooth Low Energy (BLE) devices (GATT servers). In essence, this package is an extension of the ``bluepy`` package created by Ian Harvey (see `here <https://github.com/IanHarvey/bluepy/>`_)

The aim here was to define a single object which would allow users to perform the various operations performed by the ``bluepy.btle.Peripheral``, ``bluepy.btle.Scanner``, ``bluepy.btle.Service`` and ``bluepy.btle.Characteristic`` classes of ``bluepy``, from one central place. This functionality is facilitated by the ``simpleble.SimpleBleClient`` and ``simpleble.SimpleBleDevice`` classes, where the latter is an extention/subclass of ``bluepy.btle.Peripheral``, combined with properties of ``bluepy.btle.ScanEntry``.

The current implementation has been developed in Python 3 and tested on a Raspberry Pi Zero W, running Raspbian 9 (stretch), but should work with Python 2.7+ (maybe with minor modifications in terms of printing and error handling) and most Debian based OSs.

Motivation
**********

As a newbie experimenter/hobbyist in the field of IoT using BLE communications, I found it pretty hard to identify a Python package which would enable one to use a Raspberry Pi (Zero W inthis case) to swiftly scan, connect to and read/write from/to a nearby BLE device (GATT server).

This package is intended to provide a quick, as well as (hopefully) easy to undestand, way of getting a simple BLE GATT client up and running, for all those out there, who, like myself, are hands-on learners and are eager to get their hands dirty from early on.

Limitations
***********

- As my main use-case scenario was to simply connect two devices, the current version of :class:`simpleble.SimpleBleClient` has been designed and implemented with this use-case in mind. As such, if you are looking for a package to allow you to connect to multiple devices, then know that off-the-self this package DOES NOT allow you to do so. However, implementing such a feature is an easily achievable task, which has been planned for sometime in the near future and if there proves to be interest on the project, I would be happy to speed up the process.

- Only Read and Write operations are currently supported, but I am planning on adding Notifications soon.

- Although the interfacing operations of the :class:`bluepy.btle.Service` and :class:`bluepy.btle.Peripheral` classes have been brought forward to the :class:`simpleble.SimpleBleClient` class, the same has not been done for the :class:`bluepy.btle.Descriptor`, meaning that the :class:`simpleble.SimpleBleClient` cannot be used to directly access the Descriptors. This can however be done easily by obtaining a handle of a :class:`simpleble.SimpleBleDevice` object and calling the superclass :meth:`bluepy.btle.Peripheral.getDescriptors` method.

There are a few other things that we can observe in the index.rst. First is the usage of the **** underline to specify sub-titles (or sub-sections) in our “Introduction” page. These will be shown as sub-entries to the toctree (Contents) section of our index.html file, once built. What is more, we can see how we can use the :class: and :meth: directive options, when we wish to refer to specific classed and methods. In case of classes and methods defined within our modules, the :class:: and :meth: directives also create clickable links to the corresponding classes and methods of our documentation.

Next, we create a examples.rst file in the same directory, which contains a working example, which shows how our simpleble module can be used:

Examples
=============

Installation/Usage:
*******************
As the package has not been published on PyPi yet, it CANNOT be install using pip.

For now, the suggested method is to put the file `simpleble.py` in the same directory as your source files and call ``from simpleble import SimpleBleClient, SimpleBleDevice``.

``bluepy`` must also be installed and imported as shown in the example below.
For instructions about how to install, as well as the full documentation of, ``bluepy`` please refer `here <https://github.com/IanHarvey/bluepy/>`_

Search for device, connect and read characteristic
**************************************************
.. code-block:: python

    """This example demonstrates a simple BLE client that scans for devices,
    connects to a device (GATT server) of choice and continuously reads a characteristic on that device.

    The GATT Server in this example runs on an ESP32 with Arduino. For the
    exact script used for this example see `here <https://github.com/nkolban/ESP32_BLE_Arduino/blob/6bad7b42a96f0aa493323ef4821a8efb0e8815f2/examples/BLE_notify/BLE_notify.ino/>`_
    """

    from bluepy.btle import *
    from simpleble import SimpleBleClient, SimpleBleDevice

    # The UUID of the characteristic we want to read and the name of the device # we want to read it from
    Characteristic_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8"
    Device_Name = "MyESP32"

    # Define our scan and notification callback methods
    def myScanCallback(client, device, isNewDevice, isNewData):
        client._yes = True
        print("#MAC: " + device.addr + " #isNewDevice: " +
            str(isNewDevice) + " #isNewData: " + str(isNewData))
    # TODO: NOTIFICATIONS ARE NOT SUPPORTED YET
    # def myNotificationCallback(client, characteristic, data):
    #     print("Notification received!")
    #     print("  Characteristic UUID: " + characteristic.uuid)
    #     print("  Data: " + str(data))

    # Instantiate a SimpleBleClient and set it's scan callback
    bleClient = SimpleBleClient()
    bleClient.setScanCallback(myScanCallback)
    # TODO: NOTIFICATIONS ARE NOT SUPPORTED YET
    # bleClient.setNotificationCallback(myNotificationCollback)

    # Error handling to detect Keyboard interrupt (Ctrl+C)
    # Loop to ensure we can survive connection drops
    while(not bleClient.isConnected()):
        try:
            # Search for 2 seconds and return a device of interest if found.
            # Internally this makes a call to bleClient.scan(timeout), thus
            # triggering the scan callback method when nearby devices are detected
            device = bleClient.searchDevice(name="MyESP32", timeout=2)
            if(device is not None):
                # If the device was found print out it's info
                print("Found device!!")
                device.printInfo()

                # Proceed to connect to the device
                print("Proceeding to connect....")
                if(bleClient.connect(device)):

                    # Have a peek at the services provided by the device
                    services = device.getServices()
                    for service in services:
                        print("Service ["+str(service.uuid)+"]")

                    # Check to see if the device provides a characteristic with the
                    # desired UUID
                    counter = bleClient.getCharacteristics(
                        uuids=[Characteristic_UUID])[0]
                    if(counter):
                        # If it does, then we proceed to read its value every second
                        while(True):
                            # Error handling ensures that we can survive from
                            # potential connection drops
                            try:
                                # Read the data as bytes and convert to string
                                data_bytes = bleClient.readCharacteristic(
                                    counter)
                                data_str = "".join(map(chr, data_bytes))

                                # Now print the data and wait for a second
                                print("Data: " + data_str)
                                time.sleep(1.0)
                            except BTLEException as e:
                                # If we get disconnected from the device, keep
                                # looping until we have reconnected
                                if(e.code == BTLEException.DISCONNECTED):
                                    bleClient.disconnect()
                                    print(
                                        "Connection to BLE device has been lost!")
                                    break
                                    # while(not bleClient.isConnected()):
                                    #     bleClient.connect(device)

                else:
                    print("Could not connect to device! Retrying in 3 sec...")
                    time.sleep(3.0)
            else:
                print("Device not found! Retrying in 3 sec...")
                time.sleep(3.0)
        except BTLEException as e:
            # If we get disconnected from the device, keep
            # looping until we have reconnected
            if(e.code == BTLEException.DISCONNECTED):
                bleClient.disconnect()
                print(
                    "Connection to BLE device has been lost!")
                break
        except KeyboardInterrupt as e:
            # Detect keyboard interrupt and close down
            # bleClient gracefully
            bleClient.disconnect()
            raise e

Now that we have generated some .rst files, we must add them to our toctree by altering the respective part of our index.rst file as such:

.. simpleble documentation master file, created by
sphinx-quickstart on Sat Mar 10 15:05:19 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

Welcome to simpleble's documentation!
=====================================

.. toctree::
:maxdepth: 2
:caption: Contents:

intro
simpleble
examples

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

Notice that it is not necessary to add the .rst extension when specifying files in the toctree. However, it is VERY IMPORTANT that the same indentation with the ..toctree:: directive is maintained, otherwise we will not have the desired output.

Finalising the documentation

When it is about time to build our documentation for the final time before publishing it, it is always a good idea to run the make clean and make html pair, to ensure we have a clean build. If we proceed to build the documentation we have generated, we can now see that 3 new entries have been added to our Contents section in index.html page, with 4 sub-entries for the “Documentation” page and 2 sub-entries for the “Introduction” and “Examples” pages. When we click on either of them, we are redirected to a new page, which shows a built representation of the corresponding *.rst file.

AND BEHOLD!!! We have generated a Sphinx documentation page for our package! Now it’s time to publish it :)