Link

Building your own NuvlaBox Peripheral Manager

There is a set of natively supported peripheral managers that you can opt to add to your NuvlaBox at installation time. Those are also open-source and can be found on GitHub here.

NuvlaBox Peripheral Managers are completely optional and should only be used if you’d like to have an automated mechanism to discover, register and manage peripheral devices that are attached to your NuvlaBox. Once registered, these peripheral devices can be visualized, managed and sometimes even directly controlled from Nuvla (like USB webcams).

Due to the wide variety of IoT sensors/actuators and generic peripheral devices, it would be an impossible task for us at SixSq to develop all the NuvlaBox Peripheral Managers to support every single peripheral out there.

This is where you come in!!

Let’s say you need a new NuvlaBox Peripheral Manager for a specific peripheral device. There are two ways you can go about it:

  1. email us at Support with your request and we’ll try our best to accommodate your needs

  2. develop your own NuvlaBox Peripheral Manager microservice

If you go option 2., you should host your code in your own public/private repository, and then deploy alongside the NuvlaBox Engine.

We support community-built NuvlaBox Peripheral Managers, so you can let us know about it and we can work together to make it official and available off the shelf from Nuvla!


Building a new NuvlaBox Peripheral Manager is quite simple. We have built a transparent and generic REST API within the NuvlaBox Engine core installation that allows you to register and manage your peripheral devices in the Nuvla-NuvlaBox ecosystem without any in-depth knowledge nor code dependencies.

There are only 2 requirements for having your microservice functioning correctly as a NuvlaBox Peripheral Manager:

a. Your microservice needs to be on the same Docker network as the NuvlaBox Agent component. This is usually guaranteed when you deploy all components at once, as described in the Installation Quickstart

b. Your code must manage the NuvlaBox peripheral devices through the management interface provided by the NuvlaBox Agent on port 80. The specification for this API can be found here

Mocking a NuvlaBox Peripheral Manager

Here’s an example of a mock peripheral manager, that you can use as a starting point to build your own.

Step 1

Create a working environment (local folder, GitHub repository, etc.) where you can put your code.

We have created a peripheral manager mock for this exercise.

Step 2

In that working environment, create your custom peripheral manager script. In our case, we’re using a simple Shell script, so let’s name our main manager script peripheral-manager-mock.sh.

peripheral-manager-mock.sh:

#!/bin/sh

# NAME: NuvlaBox Peripheral Manager Mock
# DESCRIPTION: Mock peripheral manager for demonstration purposes only
# ARGS: none


Step 2.1

Since this is an example, we make certain assumptions. One of those is that we assume we know in advance the unique local identifier of our mock peripheral device.

peripheral-manager-mock.sh:

#!/bin/sh

# NAME: NuvlaBox Peripheral Manager Mock
# DESCRIPTION: Mock peripheral manager for demonstration purposes only
# ARGS: none

# ---
# ASSUMPTION 1: our mock peripheral is a video device
# ASSUMPTION 2: when our mock peripheral is plugged, the file /dev/video0 is created
# ASSUMPTION 3: the identifier for our mock peripheral is always the same.
# IMPORTANT: assumption 3 should NOT be considered in real scenarios.
#            Make sure you infer the peripheral identifier dynamically, to avoid conflicts between peripherals
mock_peripheral_identifier="my-mock-peripheralXYZ"

IMPORTANT: unless you have a very special case, you should try to infer the peripheral identifier dynamically.

Step 3

Our script needs to interact with the NuvlaBox Agent API for managing the mock peripheral, so let’s build a couple of functions to help us to that.

NOTE: these two functions are generic, so in most cases you can literally copy and paste them into your own scripts.

peripheral-manager-mock.sh:

#!/bin/sh

# NAME: NuvlaBox Peripheral Manager Mock
# DESCRIPTION: Mock peripheral manager for demonstration purposes only
# ARGS: none

# ---
# ASSUMPTION 1: our mock peripheral is a video device
# ASSUMPTION 2: when our mock peripheral is plugged, the file /dev/video0 is created
# ASSUMPTION 3: the identifier for our mock peripheral is always the same.
# IMPORTANT: assumption 3 should NOT be considered in real scenarios.
#            Make sure you infer the peripheral identifier dynamically, to avoid conflicts between peripherals
mock_peripheral_identifier="my-mock-peripheralXYZ"


####
# NuvlaBox Agent API functions
# These are generic, so you can re-use them everywhere
####
nuvlabox_add_peripheral() {
  # Sends a POST request to the NuvlaBox Agent API, asking to register a new peripheral
  # $1 is the request payload, which must match the nuvlabox-peripheral resource JSON schema

  # Get the JSON payload
  payload="$1"
  echo "INFO: registering new NuvlaBox peripheral - ${payload}"

  agent_api="http://agent/api/peripheral"
  headers="-H content-type:application/json -H accept:application/json"

  # Make the request to the NuvlaBox Agent API
  response=$(curl -X POST ${agent_api} ${headers} -d "${payload}" --write-out "%{http_code}")

  # Extract the response and http code
  http_code=$(echo ${response} | awk '{print $NF}')
  output=$(echo ${response} | awk '{$NF=""; print $0}')

  if [[ "${http_code}" = "201" ]]
  then
    echo "INFO: successfully registered new peripheral $(echo ${output} | jq -re '."resource-id"')"
  else
    echo "ERR: could not register new peripheral! Reason: ${response}"
  fi
}

nuvlabox_delete_peripheral() {
  # Sends a DELETE request to the NuvlaBox Agent API, asking to delete a peripheral
  # $1 is the peripheral's local identifier, as passed in the original POST request

  # Get the identifier
  identifier="$1"
  echo "INFO: deleting NuvlaBox peripheral ${identifier}"

  agent_api="http://agent/api/peripheral"
  headers="-H accept:application/json"

  # Make the request to the NuvlaBox Agent API
  response=$(curl -X DELETE "${agent_api}/${identifier}" ${headers} --write-out "%{http_code}")

  # Extract the response and http code
  http_code=$(echo ${response} | awk '{print $NF}')
  output=$(echo ${response} | awk '{$NF=""; print $0}')

  if [[ "${http_code}" = "200" ]]
  then
    echo "INFO: successfully deleted peripheral ${identifier}"
  else
    echo "ERR: could not delete peripheral ${identifier}! Reason: ${response}"
  fi
}
####
# End of NuvlaBox Agent API functions
####

Step 4

Now to the actual peripheral discovery part of our script. According to our assumptions, our mock peripheral can be discovered via the existence of a certain file in the filesystem - /dev/video0.

So it’s quite a simple use case - all we need to do is to continuously watch that file, and trigger one of the functions from Step 3 above whenever the file is created or deleted.

The inotify-tools package gives us a very useful tool for this purpose: inotifywait

So our main code block would look something like this:

inotifywait -m -e create -e delete /dev |
while read -r directory event file
do
  # We assume we only care about /dev/video0
  if [[ "${file}" = "video0" ]]
  then
    # If the mock peripheral was plugged in, then we want to register the new NuvlaBox peripheral
    # otherwise, we want to remove it
    if [[ "${event}" = "CREATE" ]]
    then
      #...do something
    fi

    if [[ "${event}" = "DELETE" ]]
    then
      #...do something
    fi
  fi
done

Whenever the mock peripheral device is plugged, the CREATE event will be triggered and we’ll need to:

  1. build the nuvlabox-resource JSON payload for that peripheral, according to the schema represented in Nuvla API schema for NuvlaBox peripherals
  2. send a POST request to the NuvlaBox Agent API, to register the new peripheral

And, whenever the mock peripheral is unplugged, the DELETE event will be triggered, so we need to:

  1. send a DELETE request to the NuvlaBox Agent API with the corresponding peripheral identifier

Here’s our final peripheral-manager-mock.sh script!

peripheral-manager-mock.sh:

#!/bin/sh

# NAME: NuvlaBox Peripheral Manager Mock
# DESCRIPTION: Mock peripheral manager for demonstration purposes only
# ARGS: none

# ---
# ASSUMPTION 1: our mock peripheral is a video device
# ASSUMPTION 2: when our mock peripheral is plugged, the file /dev/video0 is created
# ASSUMPTION 3: the identifier for our mock peripheral is always the same.
# IMPORTANT: assumption 3 should NOT be considered in real scenarios.
#            Make sure you infer the peripheral identifier dynamically, to avoid conflicts between peripherals
mock_peripheral_identifier="my-mock-peripheralXYZ"


####
# NuvlaBox Agent API functions
# These are generic, so you can re-use them everywhere
####
nuvlabox_add_peripheral() {
  # Sends a POST request to the NuvlaBox Agent API, asking to register a new peripheral
  # $1 is the request payload, which must match the nuvlabox-peripheral resource JSON schema

  # Get the JSON payload
  payload="$1"
  echo "INFO: registering new NuvlaBox peripheral - ${payload}"

  agent_api="http://agent/api/peripheral"
  headers="-H content-type:application/json -H accept:application/json"

  # Make the request to the NuvlaBox Agent API
  response=$(curl -X POST ${agent_api} ${headers} -d "${payload}" --write-out "%{http_code}")

  # Extract the response and http code
  http_code=$(echo ${response} | awk '{print $NF}')
  output=$(echo ${response} | awk '{$NF=""; print $0}')

  if [[ "${http_code}" = "201" ]]
  then
    echo "INFO: successfully registered new peripheral $(echo ${output} | jq -re '."resource-id"')"
  else
    echo "ERR: could not register new peripheral! Reason: ${response}"
  fi
}

nuvlabox_delete_peripheral() {
  # Sends a DELETE request to the NuvlaBox Agent API, asking to delete a peripheral
  # $1 is the peripheral's local identifier, as passed in the original POST request

  # Get the identifier
  identifier="$1"
  echo "INFO: deleting NuvlaBox peripheral ${identifier}"

  agent_api="http://agent/api/peripheral"
  headers="-H accept:application/json"

  # Make the request to the NuvlaBox Agent API
  response=$(curl -X DELETE "${agent_api}/${identifier}" ${headers} --write-out "%{http_code}")

  # Extract the response and http code
  http_code=$(echo ${response} | awk '{print $NF}')
  output=$(echo ${response} | awk '{$NF=""; print $0}')

  if [[ "${http_code}" = "200" ]]
  then
    echo "INFO: successfully deleted peripheral ${identifier}"
  else
    echo "ERR: could not delete peripheral ${identifier}! Reason: ${response}"
  fi
}
####
# End of NuvlaBox Agent API functions
####


######
# MAIN
######

# We are only interested in getting notified whenever our mock peripheral is plugged and unplugged
inotifywait -m -e create -e delete /dev |
while read -r directory event file
do
  # We assume we only care about /dev/video0
  if [[ "${file}" = "video0" ]]
  then
    # If the mock peripheral was plugged in, then we want to register the new NuvlaBox peripheral
    # otherwise, we want to remove it
    if [[ "${event}" = "CREATE" ]]
    then
      echo "EVENT: ${file} has been created"
      # A new mock peripheral has been plugged, so let's categorize it
      # TIP: check the API documentation in Nuvla for a full representation of the supported peripheral schema
      peripheral="{
        \"identifier\": \"${mock_peripheral_identifier}\",
        \"classes\": [\"video\"],
        \"available\": true,
        \"name\": \"Mock Peripheral\"
      }"

      # Ask the NuvlaBox Agent to register the new mock peripheral
      nuvlabox_add_peripheral "${peripheral}"
    fi

    if [[ "${event}" = "DELETE" ]]
    then
      echo "EVENT: ${file} has been deleted"
      # Ask the NuvlaBox Agent to remove the mock peripheral
      nuvlabox_delete_peripheral "${mock_peripheral_identifier}"
    fi
  fi
done

Step 5

Now that we’ve built our custom NuvlaBox Peripheral Manager for Mock peripherals (congrats), it’s time to package our code.

Let’s build a Docker image that can be installed with the rest of the NuvlaBox Engine component.

We want it to be small. So let’s use Alpine, install our dependencies(jq and curl for the NuvlaBox Agent API functions, plus inotify-tools for watching the mock peripheral file), and copy our script into it.

Build the following Dockerfile.

Dockerfile:

FROM alpine:3.9

RUN apk add --no-cache inotify-tools jq curl

WORKDIR /opt/nuvlabox-mock-peripheral-manager

COPY peripheral-manager-mock.sh .

CMD ["/bin/sh", "peripheral-manager-mock.sh"]

Step 6

We are now ready to build our Docker image.

Simple build

Just run docker build . -t <your_image_name>. And the push the Docker image: docker push <your_image_name>.

Multi-platform build

Make sure you have docker buildx. If not, have a look at: https://docs.docker.com/buildx/working-with-buildx/

# Create the build context:
docker buildx create --name multiplatformbuilder --use

# Bootstrap the context and make sure you're targeted platforms are supported by it
docker buildx inspect --bootstrap

# Build and push the Docker image
# Feel free to replace the platforms below by the ones you're targeting
docker buildx build --platform linux/arm/v6,linux/arm/v7,linux/amd64,linux/arm64 -t <your_image_name> . --push

Step 7

We need a compose file to go alongside the other NuvlaBox Engine compose files. Remember to use your Docker image from Step 6, and to bind mount (read-only) the host’s /dev directory.

docker-compose.mock.yml:

version: "3.6"

x-common: &common
  stop_grace_period: 4s
  logging:
    options:
      max-size: "250k"
      max-file: "10"
  labels:
    - "nuvlabox.peripheral.component=True"
    - "nuvlabox.peripheral.type=mock"

services:
  peripheral-manager-mock:
    <<: *common
    # Remeber to change the Docker image to match your own custom peripheral manager
    image: nuvlabox/peripheral-manager-mock
    restart: on-failure
    volumes:
      - /dev:/dev:ro

Step 8

Finally, we can launch the custom NuvlaBox Peripheral Manager for Mock peripherals.

Just add -f docker-compose.mock.yml to your NuvlaBox Engine installation command (as described in NuvlaBox Installation and that’s it!

NOTE: if you want to deploy your custom NuvlaBox Peripheral Manager after the NuvlaBox Engine has been installed, then please make sure that your container runs within the same Docker network as the NuvlaBox Agent. To do so, check in which Docker network your NuvlaBox Agent is running, via docker network ls and docker inspect <agent_container_id>, and add that network to your peripheral manager container, via the Compose property networks (see Docker Compose docs).


Copyright 2020, SixSq