Step 1: Prerequisites¶
What you need to know¶
This tutorial assumes that:
- You have access to the SNAP Thing Services, 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 SNAP Thing Services, please refer to the “Getting Started Guide”. For more information about working with RESTful APIs, please refer to the article, “RESTful Web Services: A Tutorial”.
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 SNAP Thing Services is running, type:
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 SNAP Thing Services, make the following request:
-
POST /api/v1/deviceTypes body
{
"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:
-
GET /api/v1/deviceTypes response
{
"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:
-
POST /api/v1/devices body
{
"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:
-
GET /api/v1/devices response
{
"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 SNAP Thing Services¶
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:
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:
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.
Once you have a SNAPpy image, you’re ready to go! Almost...
Uploading the SNAPpy image
To upload the SNAPpy image to the SNAP Thing Services, 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:
{
"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:
-
POST /api/v1/images successful response
{
"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:
-
GET /api/v1/firmware response
{
"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:
-
GET /api/v1/firmware/latest response
{
"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 SNAP Thing Services 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:
-
POST /api/v1/networkConfigurations body
{
"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:
-
POST /api/v1/networkConfigurations successful response
{
"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 SNAP Thing Services with a body like:
-
POST /api/v1/networkConfigurationUpdates body
{
"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:
-
GET /api/v1/devices response during upgrade
{
"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:
-
GET /api/v1/devices?expand=firmware,image response after successful upgrade
{
"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 library to simplify making HTTP requests to the SNAP Thing Services APIs. You should install the requests library by typing:
pip install requests
Also note that we assume in this example program that your SNAP Thing Services installation is configured with configured with a self-signed SSL certificate, and thus we disable certificate verification when creating the session:
session = requests.Session()
session.auth = auth
session.verify = False
Note that we’re setting session.verify to False. If your SNAP Thing Services 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” and “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 SNAP Thing Services before deploying it into a production environment. Failing to do so subjects clients of the SNAP Thing Services to the risk that they could be talking to some other server that is impersonating the SNAP Thing Services (a so-called “Man in the Middle” attack).
Example Code¶
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()