Cisco IOx

How to use this page

Please read the instructions on this page in the order they are presented. A section may depend on a section before it and we will refrain from repeating information.


It is assumed that you are familiar with Cisco IOx and have an IOx device, such as one of the 800 series devices, configured and ready.

You will also need to have gained access to a package which contains a distribution of the LoRaWAN Network Server of xDP.

Installing xDP


Installing xDP consists of the following major activities:

  1. Install streambed-cli.
  2. Create Docker images by using streambed install.
  3. Load lora-server into Docker.
  4. Package/install/activate/start jsaas.
  5. Package/install/activate/configure/start fdp.
  6. Provision an initial user identity.
  7. Configure Network
  8. Provision an admin user
  9. Provision Control Center secrets

streambed-cli is a suite of command line tools that communicate with xDP’s services, including control-center and streambed-mqtt-gateway, and can produce Docker images for xDP services. The tools are intended for use alongside the Cisco ioxclient tools.

jsaas is a service that xDP uses to evaluate custom JavaScript programs.

fdp bundles all of the JVM-based xDP services into a single package for deployment to IOx. This consists of the following services:

  • streambed-durable-queue-server is an application that persists and streams sensor and platform data using the MQTT protocol.
  • lora-server is xDP’s application that provides a LoRaWAN Network Server, soil moisture/temperature services (including a web app for observing sensors), sensor registration services (including a web app for provisioning) and an MQTT gateway for integration with Kinetic DCM.
  • control-center is a web application that serves as the primary interface for xDP. With it, you can visualize sensor data, create custom transformers and dashboards, and provision provision LoRaWAN sensors. The application can be interacted with either using the streambed-cli or via a web user interface.
  • streambed-mqtt-gateway enables xDP to send sensor data to and receive sensor data from Cisco Kinetic DCM. From DCM, data can be routed anywhere a customer desires, and vice-versa.
  • fdp-soilstate-ui is an optional reference web application that visualizes soil moisture/temperature observations. It also contains a transformer service that transforms LoRaWAN packets from soil moisture/temperature sensors into a domain representation.

1. streambed-cli

Note that if you’ve been through the getting started experience then you can skip this section as you’ve already installed the command line tools.

Please select the type of installation and follow the instructions.

# Setup Repository
wget -qO - | \
  sudo apt-key add - && \
  echo "deb [arch=amd64] stable main" | \
  sudo tee /etc/apt/sources.list.d/streambed.list && \
  sudo apt-get install apt-transport-https -y && \
  sudo apt-get update
# Install the streambed-cli
sudo apt-get && sudo apt-get install streambed-cli
# Setup Repository
sudo tee /etc/yum.repos.d/streambed.repo << EOT
# Install the streambed-cli
sudo yum install streambed-cli
# Setup Repository
brew tap streambed/repository
# Install the streambed-cli (use `brew upgrade` if installed)
brew install streambed/repository/streambed-cli
Download the latest win-<arch>.zip file and extract it to a directory on your PATH.
You can find the files here:

Two commands in particular are now at your disposal: streambed and lora. We’ll use these shortly.

2. Create Docker images

To download the latest application images, run the following command:

streambed install

This will ensure that the latest images have been pulled down and tagged with the appropriate tag.

Setup secrets

When packaging IOx applications, you must sign them with an application key. Refer to the Cisco documentation for how to set up app keys and trust certs.

This steps declares some environment variables that will be used to locate various keys and their certificates for signing your applications. Substitute the two /path-to references with the actual path on your local system.

export APP_KEY_PATH=/path-to/app-key.pem
export APP_CERT_PATH=/path-to/app-cert.pem

An X.509 certificate used to configure an SSL connection for the Local Manager must be made available to each application that needs to communicate with various IOx services, such as the Secure Storage Service. The X.509 certificate must be in PEM format, have the name caf_cert.pem and caf_key.pem, and be signed for the following:

  • localhost

The following commands will set the variables required by the rest of the guide. Be sure to substitute the /path-to reference with the actual path on your local system.

export CAF_CERT_PATH=/path-to/caf_cert.pem
export CAF_KEY_PATH=/path-to/caf_key.pem

3. Load lora-server

lora-server is provided as a separate download. Once acquired, you’ll need to load it into Docker. Be sure to substitute /path-to/lora-server.tar with the actual path of the file on the system.

docker load < /path-to/lora-server.tar.gz

Tag the docker image with latest for easy usage:

docker tag lora-server-iox-sss:<IMAGE-TAG> streambed-cli/amd64/lora-server-iox-sss:latest

4. Install JSaaS

JSaaS (jsaas) is used to evaluate dynamically provisioned applications.

Packaging for IOx for JSaaS

First, change your current directory into its packaging directory:

mkdir -p ~/.streambed/cli/install/amd64/jsaas && cd ~/.streambed/cli/install/amd64/jsaas

Then, use streambed package prepare a Docker image:

streambed package --name jsaas --base-image linux \
    --label cisco.resources.profile=custom \
    --label cisco.resources.cpu=100 \
    --label cisco.resources.memory=64 \
    --label \
    --label[9880] \
    --label cisco.env.JSAAS_TLS_PUBLIC_CERTIFICATE_PATH=/data/appdata/caf_cert.pem \
    --label cisco.env.JSAAS_TLS_PRIVATE_KEY_PATH=/data/appdata/caf_key.pem \
    --label cisco.env.JSAAS_BIND_ADDR= \
    --image streambed-cli/amd64/jsaas:latest

Then, use ioxclient to create and install the package:

rm -f package.yaml && ioxclient docker package \
  --rsa-key "$APP_KEY_PATH" \
  --certificate "$APP_CERT_PATH" \
  streambed-cli/amd64/jsaas:latest .
ioxclient app install jsaas package.tar

Activation for JSaaS

You’ll need to create an activation.json file that configures the application so that it can be reached from other services:

cat > activation.json <<EOF
  "resources": {
    "network": [
        "interface-name": "eth0",
        "ipv4": {
          "default": true,
          "dns": "",
          "gateway": "",
          "ip": "",
          "prefix": "24"
        "mode": "static",
        "network-name": "iox-bridge0"
ioxclient app activate jsaas --payload activation.json

Install the IOx X.509 certificate for JSaaS

Install the certificates so that the application can communicate with IOx services over TLS.

ioxclient app appdata upload jsaas "$CAF_CERT_PATH" caf_cert.pem

Install the corresponding IOx X.509 key for JSaaS

The private key to the X.509 certificate used must thereby be provided:

ioxclient app appdata upload jsaas "$CAF_KEY_PATH" caf_key.pem

Start JSaaS

The service is now configured and ready to start. To start it:

ioxclient app start jsaas

5. Install xDP

xDP is installed as a single package on IOx that contains a number of services.

Packaging for IOx

First, change your current directory into its packaging directory:

mkdir -p ~/.streambed/cli/install/amd64/fdp && cd ~/.streambed/cli/install/amd64/fdp

Next, declare some environment variables pertaining to the site’s network and credentials. Be sure to substitute in the site’s id, username, and password.

export NETID=00000013
export CLOUD_SITE_ID=demo
export CLOUD_USERNAME=myclouduser
export CLOUD_PASSWORD=mycloudpassword

Note that 00000013 is as used by The Things Network. Yours may be different.

…then package xDP using its Docker image:

The following command bundles all services into an xDP package. If there are any other services that need to be installed, be sure to specify them with additional --image flags. In particular, note the xDP Soilstate UI section at the end of this document.

streambed package --name fdp --base-image linux-java \
    --label cisco.resources.profile=custom \
    --label cisco.resources.cpu=500 \
    --label cisco.resources.memory=512 \
    --label \
    --label[5732,9870,9871,9872,9873,9874,9875,9876,9877,9878,9879] \
    --label[1690,1692] \
    --label "cisco.resources.visualization=\"'true'\"" \
    --label cisco.resources.devices.0.type=usbdev \
    --label cisco.resources.devices.0.label=EXTERNAL_STORAGE \
    --label cisco.resources.devices.0.function=storage \
    --label "cisco.resources.devices.0.mandatory=\"'true'\"" \
    --label cisco.resources.devices.0.mount-point=/external-storage \
    --label cisco.env.FORCE_ROOT=1 \
    --label cisco.env.FORCE_SSS_STUB=1 \
    --label "cisco.env.JAVA_OPTS=\"''\"" \
    --image streambed-cli/amd64/streambed-durable-queue-server:latest \
        --property streambed.durable-queue.remote.server.tcp.tls.bind.enabled=true \
    --image streambed-cli/amd64/iox-sss-stub-server:latest \
        --property streambed.http-server.tls.bind.enabled=true \
    --readiness-check tcp://localhost:9878 \
    --image streambed-cli/amd64/control-center-iox-sss:latest \
        --property \
        --property streambed.http-server.bind.port=9871 \
        --property streambed.http-server.tls.bind.enabled=on \
        --property \
        --property streambed.http-server.tls.bind.port=9870 \
        --property control-center.jsaas.scheme=https \
        --property \
        --property control-center.jsaas.port=9880 \
        --property"https://$" \
        --property"$CLOUD_USERNAME" \
        --property"$CLOUD_PASSWORD" \
        --property control-center.replicator.enabled=on \
        --property control-center.replicator.scheme="https" \
        --property"" \
        --property control-center.replicator.port=443 \
        --property control-center.replicator.namespace="$CLOUD_SITE_ID" \
        --property control-center.replicator.credentials.username="$CLOUD_USERNAME" \
        --property control-center.replicator.credentials.password="$CLOUD_PASSWORD" \
		--property akka.http.server.default-host-header="$CLOUD_SITE_ID" \
        --readiness-check tcp://localhost:9876 \
    --image streambed-cli/amd64/lora-server-iox-sss:latest \
        --property lora-server.downlink.bind-host= \
        --property lora-server.downlink.bind-port=1692 \
        --property lora-server.uplink.bind-host= \
        --property lora-server.uplink.bind-port=1690 \
        --property "lora-server.netID=$NETID" \
        --readiness-check tcp://localhost:9870

The streambed package command above ensures that the image is entirely suitable for IOx by including an operating system (Alpine) and a JVM.

Finally, use ioxclient to create and install the package:

rm -f package.yaml && ioxclient docker package \
  --rsa-key "$APP_KEY_PATH" \
  --certificate "$APP_CERT_PATH" \
  streambed-cli/amd64/fdp:latest .
ioxclient app install fdp package.tar

If you run out of storage space on your IOx device then be sure to use the ioxclient layer delete command to remove all unused layers.


xDP requires external USB storage for its data so that the limited space of the internal flash file systems of IOx devices does not become full. Additionally, it requires binding to a specific IP so that its lora-server component can be discovered.

A single partition, FAT32 formatted USB device needs to be inserted into a port provided by the IOx device, and then its device details need to be located. Ensure that the USB device is plugged in. You can check for its availability using the following command:

ioxclient platform device list

Look for an entry similar to the following:

 "usbdev": [
   "available": true,
   "bus": "1",
   "dev": "5",
   "device_class": "00\n",
   "device_id": "/dev/bus/usb/001/005",
   "device_name": "/dev/sdc1",
   "device_path": "/sys/devices/pci0000:00/0000:00:09.0/usb1/1-1",
   "generic_params": null,
   "is_generic": false,
   "is_storage": true,
   "pid": "04a1",
   "port": null,
   "storage_params": {
    "device_name": "/dev/sdc1",
    "fstype": "vfat",
    "model": "Nano",
    "mount_point": "/mnt/host/usbstorage/sdc1",
    "vendor": "Imation"
   "support_fstype": [
   "type": "usbdev",
   "used_by": "streambeddurablequeueserver",
   "vid": "0718"

In particular, you should note the device_id, device_name, pid and vid fields, and substitute them in a file named activation.json as follows:

Note that the following assumes that IOx is running on the subnet. If your IOx subnet is different then please subsitute it accordingly.

  "resources": {
    "devices": [
        "type": "usbdev",
        "label": "EXTERNAL_STORAGE",
        "device-id": "/dev/bus/usb/001/002",
        "device-name": "/dev/sdc1",
        "vid": "0718",
        "pid": "04a1"
    "network": [
        "interface-name": "eth0",
        "ipv4": {
          "default": true,
          "dns": "",
          "gateway": "",
          "ip": "",
          "prefix": "24"
        "mode": "static",
        "network-name": "iox-bridge0"

Now that we have our activation configuration we can activate xDP:

ioxclient app activate fdp --payload activation.json

Install the IOx X.509 certificate

Install the certificates so that the application can communicate with IOx services over TLS.

ioxclient app appdata upload fdp "$CAF_CERT_PATH" caf_cert.pem

Install the corresponding IOx X.509 key

The private key to the X.509 certificate used must thereby be provided:

ioxclient app appdata upload fdp "$CAF_KEY_PATH" caf_key.pem

Start xDP

xDP is now configured and ready to start. To start it:

ioxclient app start fdp

6. Provision an initial user identity

At this stage there are no users who are able to authenticate with xDP. We must now create one. To do this we login to xDP’s container and create a user by sending a request directly to the IOx Secret Storage Service using its credentials.

We login using ioxclient:

ioxclient app console fdp

Once logged in, we need to establish the environment variables that belong to the container so we can gain access the IOx Secret Storage Service:

source data/.env

We will need curl:

apk add --no-cache curl

The following commands are documented at DevNet.

Provision a master secret

Our Streambed applications use a proxy secret store to improve the performance of the IOx one, thus only a master secret is kept in the actual IOx Secret Storage Service. We need to set it with the following commands:

Note that if this is an upgrade, and not an installation then you should use the same key as last time, and not generate a new one. If you do not then you will not have access to the data that you had before.

SS_KEY=$(cat /dev/urandom | tr -dc 'A-F0-9' | fold -w 32 | head -n 1)

Note the key produced for performing subsequent upgrades. DO NOT LOSE THIS KEY!!! (use echo $SS_KEY to see the key)

curl -v -k \
  -H 'Expect:' -H 'content-type:multipart/form-data' \
  -F ss-token="$(curl -v -k "$CAF_SS_IP_ADDR:$CAF_SS_PORT/SS/TOKEN/$CAF_APP_ID/$CAF_SYSTEM_UUID")" \
  -F object-type=Object \
  -F object-name=secrets.iox-sss-stub-events.key \
  -F ss-content="$SS_KEY"

You must receive an HTTP/1.1 200 OK response. If you do not then please ensure that you copied the token and secret correctly. It can be easy to make a mistake.

Provision a temporary user

Next, we need to override the location of the IOx SSS to point to our proxy server:

export CAF_SS_IP_ADDR=localhost
export CAF_SS_PORT=9876

Now, obtain a token:


We now create a user named temp with an insecure password of password. We will shortly delete this user once a new one is setup.

curl -v -k \
  -H 'Expect:' -H 'content-type:multipart/form-data' \
  -F ss-token="$SS_TOKEN" \
  -F object-type=Object \
  -F object-name=default/secrets.users.temp.password \
  -F ss-content=5bc0be4d1aaedb5b513449a59f85da3bd668c1e581d5a8a8129471f1b1b9521692ea588f8a7ec5a7884a7bfbe85875011d0c33d755541f0d2d91e51b08d40687ac02d8391466dac1cdb3a8ff

You must receive an HTTP/1.1 200 OK response. If you do not then please ensure that you copied the token and secret correctly. It can be easy to make a mistake.

You have now provisioned a user named temp.

Provision an application secret

Following on from provisioning a primary user, you must now associate a secret with the application. The secret is used to sign any tokens that may be issued and is done to prevent tampering.

export APP_SECRET=$(cat /dev/urandom | tr -dc 'A-F0-9' | fold -w 32 | head -n 1)
curl -v -k \
  -H 'Expect:' -H 'content-type:multipart/form-data' \
  -F ss-token="$SS_TOKEN" \
  -F object-type=Object \
  -F object-name=default/streambed.application-secret \
  -F ss-content="$APP_SECRET"

Exit out of the console and back to your machine.

7. Configure Network

When configuring IOS, also consider allocating the maximum amount of CPU to IOx.

xDP presents services on TCP ports 9870 to 9879. IOS must be configured to NAT these ports to an interface that will be used to serve it up; typically an interface where the streambed and lora commands alongside the ioxclient may reach it.

Use the show run command to show existing configuration

To NAT to the outside world assuming a network (using IOS), assuming that the WAN port is assigned to VLAN 600:

config terminal
ip nat inside source static tcp 9870 interface vlan 600 9870
ip nat inside source static tcp 9871 interface vlan 600 9871
ip nat inside source static tcp 9872 interface vlan 600 9872
ip nat inside source static tcp 9873 interface vlan 600 9873
ip nat inside source static tcp 9874 interface vlan 600 9874
ip nat inside source static tcp 9875 interface vlan 600 9875
ip nat inside source static tcp 9876 interface vlan 600 9876
ip nat inside source static tcp 9877 interface vlan 600 9877
ip nat inside source static tcp 9878 interface vlan 600 9878
ip nat inside source static tcp 9879 interface vlan 600 9879
write mem

Exit IOS by typing exit.

Additionally, we need to configure a packet forwarder to use the address of the Network Server (NS). The NS binds to two UDP ports which are 1690 and 1692 by default. Given this and that we configured the NS to bind to, you should now able to configure your packet forwarder.

8. Provision an admin user

This step will use the temp user with the insecure password we created earlier. We will create a new user and then remove the temp one.

Note that it will take about 5 minutes for xDP to startup on an IR800 series router and similar resource constrained devices. If you experience connectivity issues then it is likely that the application is still starting up. To track the progress of startup, login to the GOS and use virsh console fdp to view the application’s output. When ready, you will see that various ports have been bound to.

We first establish an environment for streambed command line tool communication. For example, if your router is at then the commands will be:


Now login as the temp user with as password of password:

Note that we use the --insecure option as we are using a self-signed certificate.

streambed --insecure login

Supposing your user is cisco, the following command will prompt you for a password:

streambed --insecure user add cisco

Now that you have that user setup, remove the insecure one we setup earlier.

streambed --insecure user rm temp

9. Provision Control Center secrets

xDP’s Control Center encrypts its transformers and dashboards using secrets that need to be created.

Additionally, the LoRa server needs a key to encrypt its end device data.

First, generate three random secrets:

export END_DEVICES_SECRET="$(LC_CTYPE=C tr -dc 'A-F0-9' < /dev/urandom | fold -w 32 | head -n 1)"
export TRANSFORMERS_SECRET="$(LC_CTYPE=C tr -dc 'A-F0-9' < /dev/urandom | fold -w 32 | head -n 1)"
export DASHBOARDS_SECRET="$(LC_CTYPE=C tr -dc 'A-F0-9' < /dev/urandom | fold -w 32 | head -n 1)"
export LAYERS_SECRET="$(LC_CTYPE=C tr -dc 'A-F0-9' < /dev/urandom | fold -w 32 | head -n 1)"

Then, create the secrets:

streambed --insecure secret add secrets.lora-server.end-devices.key "$END_DEVICES_SECRET"
streambed --insecure secret add secrets.dashboards-events.key "$DASHBOARDS_SECRET"
streambed --insecure secret add secrets.layer-events.key "$LAYERS_SECRET"
streambed --insecure secret add secrets.transformer-program-events.key "$TRANSFORMERS_SECRET"

Other applications - fdp-soilstate-ui (optional)

This application contains both a UI and a transformer for observing soil sensor moisture data.

A transformer is provided for every new type of sensor in the system. Substitute this section for sensors in your system. Soil moisture/temperature is a reference sensor and serves as a useful example. You will typically have more types of sensor and repeat this, and other parts of the remaining document for your sensors. The following assumes that the soilstate transformer has been published to your local Docker repository.

User interface services are required so that sensor data can be visualized. Repeat this section for each user interface to be installed (which is almost the same as installing any other type of service).

Other packaging for IOx

When creating the xDP package for IOx (noted above), you can include the Soilstate UI by adding an --image flag, for example:

streambed package --name fdp \
    --image streambed-cli/amd64/fdp-soilstate-ui:latest --property --property streambed.http-server.bind.port=9871 \