Building your own NuvlaEdge Peripheral Manager
There is a set of natively supported peripheral managers that you can opt to add to your NuvlaEdge at installation time. Those are also open-source and can be found on GitHub here.
NuvlaEdge 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 NuvlaEdge. 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 NuvlaEdge Peripheral Managers to support every single peripheral out there.
This is where you come in!!
Let’s say you need a new NuvlaEdge Peripheral Manager for a specific peripheral device. There are two ways you can go about it:
-
email us at Support with your request and we’ll try our best to accommodate your needs
-
develop your own NuvlaEdge Peripheral Manager microservice
If you go option 2, you should host your code in your own public/private repository, and then deploy alongside the NuvlaEdge software.
We support community-built NuvlaEdge 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 NuvlaEdge Peripheral Manager is quite simple. We have built a transparent and generic REST API within the NuvlaEdge core installation that allows you to register and manage your peripheral devices in the Nuvla-NuvlaEdge ecosystem without any in-depth knowledge nor code dependencies.
There are only 2 requirements for having your microservice functioning correctly as a NuvlaEdge Peripheral Manager:
a. Your microservice needs to be on the same Docker network as the NuvlaEdge 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 NuvlaEdge peripheral devices through the management interface provided by the NuvlaEdge Agent on port 80. The specification for this API can be found here
Mocking a NuvlaEdge 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: NuvlaEdge 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: NuvlaEdge 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 NuvlaEdge 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: NuvlaEdge 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"
####
# NuvlaEdge Agent API functions
# These are generic, so you can re-use them everywhere
####
nuvlabox_add_peripheral() {
# Sends a POST request to the NuvlaEdge 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 NuvlaEdge peripheral - ${payload}"
agent_api="http://agent/api/peripheral"
headers="-H content-type:application/json -H accept:application/json"
# Make the request to the NuvlaEdge 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 NuvlaEdge 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 NuvlaEdge peripheral ${identifier}"
agent_api="http://agent/api/peripheral"
headers="-H accept:application/json"
# Make the request to the NuvlaEdge 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 NuvlaEdge 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 NuvlaEdge 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:
- build the nuvlabox-resource JSON payload for that peripheral, according to the schema represented in Nuvla API schema for NuvlaEdge peripherals
- send a POST request to the NuvlaEdge Agent API, to register the new peripheral
And, whenever the mock peripheral is unplugged, the DELETE event will be triggered, so we need to:
- send a DELETE request to the NuvlaEdge Agent API with the corresponding peripheral identifier
Here’s our final peripheral-manager-mock.sh script!
peripheral-manager-mock.sh:
#!/bin/sh
# NAME: NuvlaEdge 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"
####
# NuvlaEdge Agent API functions
# These are generic, so you can re-use them everywhere
####
nuvlabox_add_peripheral() {
# Sends a POST request to the NuvlaEdge 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 NuvlaEdge peripheral - ${payload}"
agent_api="http://agent/api/peripheral"
headers="-H content-type:application/json -H accept:application/json"
# Make the request to the NuvlaEdge 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 NuvlaEdge 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 NuvlaEdge peripheral ${identifier}"
agent_api="http://agent/api/peripheral"
headers="-H accept:application/json"
# Make the request to the NuvlaEdge 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 NuvlaEdge 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 NuvlaEdge 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 NuvlaEdge Agent to register the new mock peripheral
nuvlabox_add_peripheral "${peripheral}"
fi
if [[ "${event}" = "DELETE" ]]
then
echo "EVENT: ${file} has been deleted"
# Ask the NuvlaEdge Agent to remove the mock peripheral
nuvlabox_delete_peripheral "${mock_peripheral_identifier}"
fi
fi
done
Step 5
Now that we’ve built our custom NuvlaEdge 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 NuvlaEdge component.
We want it to be small. So let’s use Alpine, install our dependencies (jq
and curl
for the NuvlaEdge 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 NuvlaEdge 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 NuvlaEdge Peripheral Manager for Mock peripherals.
Just add -f docker-compose.mock.yml
to your NuvlaEdge installation command (as described in NuvlaEdge Installation and that’s it!
NOTE: if you want to deploy your custom NuvlaEdge Peripheral Manager after the NuvlaEdge has been installed, then please make sure that your container runs within the same Docker network as the NuvlaEdge Agent. To do so, check in which Docker network your NuvlaEdge Agent is running, via
docker network ls
anddocker inspect <agent_container_id>
, and add that network to your peripheral manager container, via the Compose propertynetworks
(see Docker Compose docs).