Getting Started

Sample Application: Getting Bridge SNAPcore Version

In this example we show how to write a Python application using SNAPconnect to retrieve the SNAPcore firmware version of a serially-connected bridge node.

"""BridgeVersion.py"""
import logging
import binascii
import sys

from snapconnect import snap


SERIAL_TYPE = snap.SERIAL_TYPE_RS232
SERIAL_PORT = "/dev/tty.usbserial-AK04R6G3"


class BridgeVersionClient(object):
    def __init__(self):
        self.bridge_address = None  # Set a default value for the bridge address
        self.bridge_version = "Unknown"  # Set a default value for the bridge version

        # You can define what functions can be called remotely on your SNAPconnect instance
        # using the `funcs` argument
        funcs = {
            "major_callback": self.major_callback,
            "minor_callback": self.minor_callback
        }

        # Create a SNAP instance
        self.comm = snap.Snap(funcs=funcs)

        # You can also define which functions can be called using `add_rpc_func`
        self.comm.add_rpc_func("patch_callback", self.patch_callback)

        # SNAPconnect also provides hooks for certain events, for example when the serial connection
        # is opened or closed
        self.comm.set_hook(snap.hooks.HOOK_SERIAL_OPEN, self.hook_open)
        self.comm.set_hook(snap.hooks.HOOK_SERIAL_CLOSE, self.hook_close)

        # Open a serial connection to your bridge
        self.comm.open_serial(SERIAL_TYPE, SERIAL_PORT)

        self.log = logging.getLogger("BridgeVersionClient")

        snapconnect_addr = self.comm.local_addr()
        self.log.info("SNAPconnect Address: %r" % binascii.hexlify(snapconnect_addr))

    def hook_open(self, serial_type, port, addr=None):
        """Callback function invoked when a serial connection is opened."""
        if addr:
            self.bridge_address = addr
            self.log.info("Serial connection opened to %s", binascii.hexlify(addr))
            self.get_version()
        else:
            self.log.critical("Unable to open serial connection")

        return addr

    def hook_close(self, serial_type, port):
        """Callback function invoked when a serial connection is closed."""
        self.log.info("Serial connection closed")

    def get_version(self):
        """Start the RPC chain to retrieve the bridge SNAPcore version."""
        self.comm.rpc(self.bridge_address, 'callback', 'major_callback', 'getInfo', 5)

    def major_callback(self, major_version):
        """Callback function invoked when the major version is returned from the bridge."""
        self.bridge_version = str(major_version) + "."
        self.comm.rpc(self.bridge_address, 'callback', 'minor_callback', 'getInfo', 6)

    def minor_callback(self, minor_version):
        """Callback function invoked when the minor version is returned from the bridge."""
        self.bridge_version += str(minor_version) + "."
        self.comm.rpc(self.bridge_address, 'callback', 'patch_callback', 'getInfo', 7)

    def patch_callback(self, patch_version):
        """Callback function invoked when the patch version is returned from the bridge."""
        self.bridge_version += str(patch_version)
        self.log.info("Bridge SNAPcore version: %s" % self.bridge_version)
        self.stop()

    def stop(self):
        """Stop the SNAPconnect instance."""
        self.comm.close_all_serial()  # Close all serial connections opened with SNAPconnect
        sys.exit(0)  # Exit the program


if __name__ == "__main__":
    # Configure Python's built-in logging module to display any info or higher level messages
    logging.basicConfig(level=logging.INFO)

    client = BridgeVersionClient()  # Instantiate a client instance

    # Start the SNAPconnect loop, nothing can happen after this point
    client.comm.loop()

Details on all of the parameters that SNAPconnect methods take are described in the SNAPconnect API section.

Let’s walk through this simple example to see how it works. The four packages we import in this example are the standard Python logging package, the sys package which we use to terminate the program, a package called binascii to print our SNAP MAC addresses in human-readable form, and the SNAPconnect package.

Once we define the BridgeVersionClient class, we instantiate the SNAPconnect instance we will use in the __init__ function (look for self.comm in the sample code). When we instantiate the SNAPconnect instance, we pass it a Python dictionary with the functions we would like to make publicly available to be called by other SNAP nodes. In this example, we register two callback functions where we want the nodes to send responses to getInfo requests.

funcs = {
    "major_callback": self.major_callback,
    "minor_callback": self.minor_callback
}

# Create a SNAP instance
self.comm = snap.Snap(funcs=funcs)

The next function we call is add_rpc_func to add another publicly available function to be called by other SNAP nodes. This demonstrates the two ways you can make functions publicly available using SNAPconnect.

self.comm.add_rpc_func("patch_callback", self.patch_callback)

Next we call set_hook to add hooks for two events that are important in our program lifecycle: when the serial connection is opened and when it is closed. SNAPconnect provides hooks for a number of other events, see the Hooks section in the SNAPconnect API documentation for more information.

self.comm.set_hook(snap.hooks.HOOK_SERIAL_OPEN, self.hook_open)
self.comm.set_hook(snap.hooks.HOOK_SERIAL_CLOSE, self.hook_close)

After we set up our functions and hooks, we call the open_serial method on our SNAPconnect instance to have it open a serial connection to the SNAP bridge node.

self.comm.open_serial(SERIAL_TYPE, SERIAL_PORT)

Finally, we create a logging instance to log important events and the output of what we receive from the SNAP bridge node.

self.log = logging.getLogger("BridgeVersionClient")

Within the main section we configure the default logger to print any logged messages to standard out.

logging.basicConfig(level=logging.INFO)

We then create an instance of the client class that we defined earlier in the sample code. At the end we call the loop method of the SNAPconnect instance to start having SNAPconnect continuously send, receive, and process SNAP messages. Until we call the loop method, SNAPconnect will not be able to do anything we ask it to do.

client = BridgeVersionClient()  # Instantiate a client instance

# Start the SNAPconnect loop, nothing can happen after this point
client.comm.loop()

To run this program, you should first change the SERIAL_TYPE and SERIAL_PORT to those of your bridge device (see the snapconnect-serial-params section for more information). Use SNAPtoolbelt to verify the bridge device is working.

At this point, you will be able to run the Python program:

python ``BridgeVersion.py``

INFO:BridgeVersionClient:SNAPconnect Address: '000020'
INFO:BridgeVersionClient:Serial connection opened to 09a199
INFO:BridgeVersionClient:Bridge SNAPcore version: 2.6.9
INFO:BridgeVersionClient:Serial connection closed

If all went well, you will see something that looks like the above messages and the program will print out the SNAPcore version of your bridge.

If your bridge node is not connected or the specified SERIAL_PORT is not correct, you may see the following error from PyserialDriver:

ERROR:snaplib.serialwrapper.PyserialDriver:An error occured while setting up the serial port

If you encounter that message, verify that your device is connected and available on the specified port.