.. synapse-network-service documentation master file, created by
   sphinx-quickstart on Thu Apr  7 09:36:08 2016.
   You can adapt this file completely to your liking, but it should at least
   contain the root `toctree` directive.


Step 1: Prerequisites
---------------------

What you need to know
_____________________

This tutorial assumes that:

+ You have access to the |project_name|, running on a gateway or server in your network; and,
+ You are familiar with the purpose and use of RESTful APIs

For more information about installing, configuring, and running the |project_name|, please refer
to the `"Getting Started Guide" <https://developer.synapse-wireless.com/thing-services/getting-started/index.html>`_. For more information about working with RESTful APIs,
please refer to the article, `"RESTful Web Services: A Tutorial" <http://www.drdobbs.com/web-development/restful-web-services-a-tutorial/240169069>`_.

Install firmware
________________

SNAP core firmware releases for SNAP-enabled devices are distributed as Python packages. If you want to upgrade
the SNAP core firmware on one or more of your SNAP devices, you'll first need to install the appropriate Python
package(s) on the server where the |project_name|is running. The |project_name|can then use
contents of these packages to upgrade your SNAP devices.

To install a SNAP core firmware distribution on the server where the |project_name| is running, type:

.. code-block:: bash

   pip install --extra-index-url https://update.synapse-wireless.com/pypi snap-firmware-2.7.4

The last three digits of the package name (here, **2.7.4**) indicate the firmware version you want to install.
To see all firmware versions available for download, you can browse to https://update.synapse-wireless.com/pypi/
and look for the package names that begin with **snap-firmware**. If you are not sure which firmware version to
install and are just setting up your network, we recommend using the latest available firmware version.
The example above installs the 2.7.4 firmware.

Step 2: Add a device type
-------------------------

We use device types to group SNAP devices according to their purposes. For example, your SNAP network may contain
some SNAP devices that are temperature sensors, and other devices that are lighting controllers. Device types
are user-defined, and a given device is associated with a single device type.

To register a new device type with the |project_name|, make the following request:

.. object:: POST /api/v1/deviceTypes body

.. code-block:: javascript

  {
    "description": "Temperature Sensor"
  }

This adds a new "Temperature Sensor" device type.We will use the device type description later
when adding a new device.

If you want to see the list of previously entered device types, send a **GET /api/v1/deviceTypes** request:

.. object:: GET /api/v1/deviceTypes response

.. code-block:: javascript

  {
     "data": [
       {
         "href": "https://localhost:3000/api/v1/deviceTypes/1",
         "id": 1,
         "description": "Temperature Sensor"
       }
     ],
     "apiVersion": "v1"
   }

Here, we've only created one device type, and so the list just contains one entry.

Step 3: Add a device
--------------------

A device is an individual SNAP-enabled device. Each SNAP radio has a unique, 6-hex character MAC address that is used
for routing messages to that device. When we add a new device, we need to make sure we use the correct MAC address to
communicate with it.

For this example, let's pretend our device's SNAP MAC address is **608477**. To add this device, make the following
request:

.. object:: POST /api/v1/devices body

.. code-block:: javascript

   {
        "description": "Upstairs Conference Room Temperature Sensor",
        "addr": "608477",
        "platform": "RF200",
        "device_type": "Temperature Sensor"
   }

Note that for the **device_type** property, we're using the description of the previously added device type.

If you want to see the list of previously entered devices, send a **GET /api/v1/devices** request:

.. object:: GET /api/v1/devices response

.. code-block:: javascript

   {
      "data": [
        {
          "href": "https://localhost:3000/api/v1/devices/1",
          "id": 1,
          "description": "Upstairs Conference Room Temperature Sensor",
          "image": {
            "href": "Unknown"
          },
          "firmware": {
            "href": "Unknown"
          },
          "upgrading": false,
          "platform": "RF200",
          "device_type": "Temperature Sensor",
          "addr": "608477"
        }
      ],
      "apiVersion": "v1"
   }

Note that this response contains a few properties that you didn't assign when you created the device.
The **image** and **firmware** properties indicate the SNAPpy image and SNAP core firmware installed on
the device; those are currently unknown, but we'll come back to those soon. We also see an **upgrading**
property, currently set to **false**. That property will come into play when we learn how to upgrade the image
and firmware installed on the device.

Step 4: Upgrade the image and firmware on your device
-----------------------------------------------------

Upload a SNAPpy image to the |project_name|
________________________________________________

**Writing a SNAPpy script**

An image, aka a SPY file, is a compiled SNAPpy script. This is your custom code that runs on a SNAP module. If
you've never written a SNAPpy script, see these resources on how to get started:

* `SNAP Users Guide <https://forums.synapse-wireless.com/upload/SNAP Users Guide 2.7.pdf>`_
* `SNAP Primer <https://forums.synapse-wireless.com/upload/SNAP Primer.pdf>`_


**Compiling a SNAPpy script into a SNAPpy image**

Before you can upload a SNAPpy script to a device, you need to compile it into a SNAPpy image (\*.spy file).
SNAPbuild is a cross-platform, command-line tool that you can use to compile SNAPpy scripts into SNAPpy images.
For example, suppose you have a SNAPpy script named **blink.py** that you want to compile into a SNAPpy image
that can run on an RF200PF1 module under SNAP core version 2.7.4. You can do this by typing the command:

.. code-block:: bash

    snapbuild blink.py --module-type RF200PF1 --core-version 2.7.4

When it completes, you'll have two new files, **blink.spy** and **blink.map**, in your current working directory.
The **blink.spy** file is the one we're interested in.

For more information on how to use SNAPbuild, `see the online documentation <http://snapbuild.readthedocs.io/en/latest/>`_.

Once you have a SNAPpy image, you're ready to go! Almost...

**Uploading the SNAPpy image**

To upload the SNAPpy image to the |project_name|, we need to encode the image's binary data as text,
using the Base64 scheme. There are a variety of command-line tools, or software libraries, that you can use
to Base64-encode your SNAPpy image; see the **Python Example** below for an example of how to do this using Python.

You can upload your SNAPpy image to the service by sending a **POST /api/v1/images** request:

.. code-block:: javascript

    {
        "name": "blink",
        "content": "/d9CprNlBqsxQ8oJ+23SCfkIrXOaoUIaxHNoVK5xo3FLuS9uDXBRO1ommFU6wmMvssqK13qPGMFBe7gc/IaZY8c6fK0KnAgY/WPZWAsQGi1xDP+L7hoi8J0lts3BCJ1czjzCE9y4H176wcKqyezON3FW/0HvYJG9WpgR18eKFe6mW3ylE5QAltxIQG8jQBlXsOU82J7G8mdsds9prCbC8A2Vqiekmgza4Q7l6MnStBilePZxqhYipsWnifMiq4UsHatKcnfZd7ohnf49wQLMS8h1t3R/WYcYfJy7VSso8g6wJ8K5LRv1l3AH1qE9ohJERfc7C1aWAq/ApYIVTBxcFt6yuytjskB1QWoXoQ6l+9qmR//H8KiQbWcw6D/j5FBW61Py1ZHU45r66FSP1FeqjCv9dwGx5qkF9OSM0g9bsObdahldQ1nS1xdV+Zg/RkVSAeeMmrFqg3tTPLslRbckDW6kj/F/KZaiqrAjjK25REMjcEv9aRg8lBiwqvxLcAev6Kd3KKH27rlqH3WhbrYBe4AUFw7kNgjPQ3gIX8jf0HJOl3PZO32FuJksQfjvO7xW94sXC0BUbXrszZ+WDKMY4rmN2YGHflE9L+yChq/DpKSuG2eahqLb915osWahSDd2RQ27aQ3SDXwd8jKwAjpxKR0FWxp8yLDgoIUFJwWJBjyun+4f+qXLE5Zvz1B1z6f06I+VMwQWjNMPo9JeA5iuyL5O+wVYDlpBsoUnY38Vsw6vLYmQNb8nnGfo1Sonu5W9O3gUTVNKVDB6F4qJLDLCgVVWXdmJGvC8qVg3n+ggbT/5yiMh6nmaLeqLMPZZViMn52jAVbIJQvcWTvttUwdH9Qpf3m7aJdpP834XPcvnrWSBg4TyOdG/M5SyJFFXDIgFQNsj6UoGEqADDi9pB1eAO/iwGGB7tciD4x/yrkHgqMd0XJPDOVZzr1h2v7IHhOtbh3szXcaGJNTwMqiwC6ihtgips1MJahpaky73VUDMWcnggoaTSOOJvEPZ8SNhgyec0wj6UhpDp9uJYbdxWFcf8ad97l9EesoyfOzIbyoS7SqpFDS/+JZFvb7MaZV8v3AQkJkr/SWs74vz3Nng6rbF8IANK7yDuoEf8LmwwZJtbXoCaZmsERt73ZprdRWztDX4n7PdF/7mJ37yVg/W4+2vPm8FqQ5BE1BwcgwTNd7/QJyhNTcNvm/BDIgZ4o/78KhQUxRsiBP85ck2WcvDf9hKlS+xixom7qY4PazAVWMRhvefg99ANuj1r3o38sTMlYOf"
    }

Here, that very long string (the value for the "content" property) is the Base64-encoded contents of our **blink.spy**
file. Yours may be different, depending on the original SNAPpy script's contents and the target module type and core
version specified in the **snapbuild** command.

If the image upload is successful, the response should look similar to the following:

.. object:: POST /api/v1/images successful response

.. code-block:: javascript

    {
      "data": {
        "href": "https://localhost:3000/api/v1/images/5418b8a9-4cc8-46fe-805d-761e5ad7a3ba",
        "id": "5418b8a9-4cc8-46fe-805d-761e5ad7a3ba",
        "crc": "0x16f1",
        "size": 960,
        "name": "blink"
      },
      "apiVersion": "v1"
    }

We need to keep track of the response's **Location** header for the next step.

Check what firmware is available
________________________________

To check what firmware is already installed on your system, we can send a **GET /api/v1/firmware** request and get a
response that looks similar to the following:

.. object:: GET /api/v1/firmware response

.. code-block:: javascript

    {
      "data": [
        {
          "href": "https://localhost:3000/api/v1/firmware/2.5.6",
          "version": "2.5.6"
        },
        {
          "href": "https://localhost:3000/api/v1/firmware/2.7.4",
          "version": "2.7.4"
        }
      ],
      "apiVersion": "v1"
    }

If we just want to see what the latest installed firmware version is, we can send a **GET /api/v1/firmware/latest**
request:

.. object:: GET /api/v1/firmware/latest response

.. code-block:: javascript

    {
      "data": {
        "href": "https://localhost:3000/api/v1/firmware/2.7.4",
        "version": "2.7.4"
      },
      "apiVersion": "v1"
    }

If you have no firmware installed, please see the **Install Firmware** instructions in Step 1 above.

Create a network configuration
______________________________

A network configuration is a snapshot of what you want your network to look like when an update is applied.
A network configuration contains one or more device group configurations. Each device group configuration
provides a way to describe what the configuration should be for a group of devices. The configuration for a
device group can tell the |project_name| to upgrade only the firmware, only the image, or both on
all devices in that group.

When you upgrade firmware, you can specify a specific version or simply use the word **latest** to install
the most recent firmware. When you upgrade an image, you need to have an image uploaded to the Synapse Network
Service already. If you haven't uploaded an image yet, see **Step 4: Upload a SNAPpy image**.

In this example, we will create a configuration that will upgrade device the device we created in Step 3 to the
latest firmware and the image we uploaded in Step 4 when this configuration is applied. To create the configuration,
send a **POST /api/v1/networkConfigurations** request to the service:

.. object:: POST /api/v1/networkConfigurations body

.. code-block:: javascript

    {
         "deviceGroupConfigurations": [{
           "configuration": {
             "firmware": {
               "href": "/api/v1/firmware/latest"
             },
             "image": {
               "href": "/api/v1/images/5418b8a9-4cc8-46fe-805d-761e5ad7a3ba"
             }
           },
           "devices": ["608477"]
         }]
     }

If the network configuration creation is successful, we should get a response like the following:

.. object:: POST /api/v1/networkConfigurations successful response

.. code-block:: javascript

    {
       "data": {
         "href": "https://localhost:3000/api/v1/networkConfigurations/5e5de722-8f8f-47ab-aba4-0af262d81e05",
         "id": "5e5de722-8f8f-47ab-aba4-0af262d81e05",
         "deviceGroupConfigurations": [
           {
             "configuration": {
               "image": {
                 "href": "/api/v1/images/50c6e2c2-9864-46f8-b75c-f7c28366fe43"
               },
               "firmware": {
                 "href": "/api/v1/firmware/2.7.4"
               }
             },
             "devices": ["608477"]
           }
         ],
       },
       "apiVersion": "v1"
     }

Keep track of the **Location** header in the response because it will be used to apply the network configuration.

Apply a network configuration
_____________________________

Once we have specified what we want the network to look like, we need to apply our configuration to start upgrading
the firmware and images.

To start upgrading, send a **POST /api/v1/networkConfigurationUpdates** request to the |project_name| with
a body like:

.. object:: POST /api/v1/networkConfigurationUpdates body

.. code-block:: javascript

    {
        "href": "/api/v1/networkConfigurations/5e5de722-8f8f-47ab-aba4-0af262d81e05"
    }

Note that depending on the number of devices being updated, this process can take anywhere from a few seconds to
several minutes. While the network configuration updates are being applied, the devices that are being upgraded
will have their **upgrading** property set to true. For example, a **GET /api/v1/devices** request during the
update might return:

.. object:: GET /api/v1/devices response during upgrade

.. code-block:: javascript

    {
       "data": [
         {
           "href": "https://localhost:3000/api/v1/devices/1",
           "id": 1,
           "description": "Upstairs Conference Room Temperature Sensor",
           "image": {
             "href": "Unknown"
           },
           "firmware": {
             "href": "Unknown"
           },
           "upgrading": true,
           "platform": "RF200",
           "device_type": "Temperature Sensor",
           "addr": "608477"
         }
       ],
       "apiVersion": "v1"
     }

When the upgrade completes successfully, the firmware and image **href** properties will be populated with the
firmware and image versions applied. We can expand the results by adding the expand query parameter for the fields
we want more information about. For example, if we want more information about both the image and the firmware,
we can send a **GET /api/v1/devices?expand=firmware,image** request:

.. object:: GET /api/v1/devices?expand=firmware,image response after successful upgrade

.. code-block:: javascript

    {
       "data": [
         {
           "href": "https://localhost:3000/api/v1/devices/1",
           "id": 1,
           "description": "Upstairs Conference Room Temperature Sensor",
           "image": {
             "href": "https://localhost:3000/api/v1/images/5418b8a9-4cc8-46fe-805d-761e5ad7a3ba",
             "id": "5418b8a9-4cc8-46fe-805d-761e5ad7a3ba",
             "size": 960,
             "crc": "0x16f1",
             "name": "blink"
           },
           "firmware": {
             "href": "https://localhost:3000/api/v1/firmware/2.7.4",
             "version": "2.7.4"
           },
           "upgrading": true,
           "platform": "RF200",
           "device_type": "Temperature Sensor",
           "addr": "608477"
         }
       ],
       "apiVersion": "v1"
     }

Python Example
--------------

The following example Python program uses the popular `requests <http://docs.python-requests.org/>`_ library to
simplify making HTTP requests to the |project_name| APIs. You should install the **requests** library by
typing:

.. code-block:: bash

    pip install requests

Also note that we assume in this example program that your |project_name| installation is configured with
configured with a self-signed SSL certificate, and thus we disable certificate verification when creating the
session:

.. code-block:: python

    session = requests.Session()
    session.auth = auth
    session.verify = False

Note that we're setting **session.verify** to **False**. If your |project_name| installation is configured
with a proper SSL certificate (i.e. one that is issued and verified by a trusted Certificate Authority), you should
set **verify** to **True** so that the requests library will verify the certificate returned from the server. See the
`"Session Objects" <http://docs.python-requests.org/en/master/user/advanced/#session-objects>`_ and
`"SSL Cert Verification" <http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification>`_
sections of the **requests** documentation for additional information about these advanced features.

**Caution:** Self-signed SSL certificates are fine for use in testing or development environments, but you should be
using legitimate, verifiable SSL certificates with the |project_name| before deploying it into a production
environment. Failing to do so subjects clients of the |project_name| to the risk that they could be talking
to some other server that is impersonating the |project_name| (a so-called "Man in the Middle" attack).

Example Code
____________

.. code-block:: python

    import base64
    import logging
    import time
    import os

    import requests
    from requests.auth import HTTPBasicAuth

    logging.basicConfig(level=logging.DEBUG)
    logging.getLogger('requests').setLevel(logging.WARN)
    logging.getLogger('urllib3').setLevel(logging.WARN)

    # Create HTTP Basic authentication credentials
    auth = HTTPBasicAuth('snap', 'Synapse$0123')

    baseUrl = 'https://localhost:3000/api/v1'  # TODO - Update this with the URL to your copy of the synapse-network-service

    snap_mac_addr = "608477"

    # Create a requests session
    session = requests.Session()
    session.auth = auth
    session.verify = False

    # Add the "Temperature Sensor" device type
    device_type = {"description": "Temperature Sensor"}

    response = session.post(baseUrl + '/deviceTypes', json=device_type)
    if response.status_code == 201:
        print "Created device type"

    # Add a "Temperature Sensor" device
    device = {
        "description": "Upstairs Conference Room Temperature Sensor",
        "addr": snap_mac_addr,
        "platform": "RF200",
        "device_type": device_type["description"]
    }

    response = session.post(baseUrl + '/devices', json=device)
    if response.status_code == 201:
        print "Created device"

    def get_base64_encoded_file_contents(path):
        """Return a Base64-encoded version of a file's contents.

        :param str path: The path to a SPY file
        :rtype: str
        :returns: the Base64-encoded contents
        """
        with open(path, 'rb') as io:
            contents = io.read()
            return base64.b64encode(contents)

    # Get the path to the image on your local machine
    path = os.path.join(os.getcwd(), "blink.spy")

    # Upload the SNAPpy image
    image = {
        "name": "blink",
        "content": get_base64_encoded_file_contents(path)
    }

    response = session.post(baseUrl + '/images', json=image)
    image_href = None
    if response.status_code == 201:
        print "Image successfully uploaded"
        # Save the image location
        image_href = response.headers["Location"]

    # Print the latest firmware version
    response = session.get(baseUrl + '/firmware/latest')
    if response.status_code == 200:
        print "Firmware version: ", response.json()['data']['version']

    # Create a network configuration
    network_configuration = {
        "deviceGroupConfigurations": [{
            "configuration": {
                "firmware": {
                    "href": "/api/v1/firmware/latest"
                },
                "image": {
                    "href": image_href
                }
            },
            "devices": [snap_mac_addr]
        }]
    }
    response = session.post(baseUrl + '/networkConfigurations', json=network_configuration)
    network_configuration_href = None
    if response.status_code == 201:
        print "Network configuration successfully uploaded!"
        # Save the image id
        network_configuration_href = response.headers["Location"]

    # Start the upgrade
    network_configuration_href = {
        "href": network_configuration_href
    }

    response = session.post(baseUrl + '/networkConfigurationUpdates', json=network_configuration_href)
    if response.status_code == 202:
        print "Starting network upgrade"

    # Check the results
    response = session.get(baseUrl + '/devices?expand=image,firmware')
    print response.json()

