Remote Lab

Version 0.0.1

Introduction

The Remote Lab framework provides ready to deploy services for managing the scheduling, programming and control of remote assets such as development boards with remotely controllable periferals. In order to interact with a remote asset, a driver for the asset MUST be provided. Drivers are written for Node.js and MUST include a function for providing basic information, a function for starting the service and a function for stopping the service. It MAY also include a remote programming service and/or a remote control service.

Remote Lab services are designed to be delpoyed on a remote computer such as a Raspberry Pi. Each remote computer provides agent services that can communicate with other remote systems to form a multi-agent system. Agents on the same network within the same broadcast domain will automatically discover each other and share information about the services they provide. Due to this agent discovery mechanism any agent can be used to schedule and access other agents.

This document provides details of the Remote Lab components and instructions for developing drivers and for interacting with the respective services.

Architecture

The Remote Lab system is divided into a series of backend services, frontend, web-based user interfaces and hardware. Front end services are typically in the form of a rich browser application that communicates with a backend ReST service. For example, the Remote Lab front end is a browser application developed with the Angular framework and communicates with the ellipsis-embedded ReST API which is a Node.js application running on the Remote Lab Server.

A high-level overview of all components of a Remote Lab are laid out below. Installation details can be found in the Deployment section below.

Client Computer

Web Browser

Remote Programming UI

e.g. Virtual Dev Board

Remote Control UI

e.g. Virtual Dev Board

Programmer Client (e.g. Vivado)

Remote Lab Server

(e.g. Raspberry Pi)

Apache Web Server

Remote Lab UI (html, css, js source | port 443)

Scheduling and device user interface.

Remote Programming UI (html, css, js source)

Device specific programming user interface; e.g. Virtual Dev Board

Remote Control UI (html, css, js source)

Device specific control user interface; e.g. Virtual Dev Board

Remote Services (ellipsis-embedded | HTTPS | port 8100)

Scheduling (json)

Device Information (json)

Device Driver (Node.js module)

  • Information: Version, Programming URL, Control URL
  • Start Trigger: Function triggered when a schedule begins.
  • Stop Trigger: Function triggered when a schedule ends.

Remote Lab UI

The Remote Lab User Interface is a browser application that provides device scheduling services and is the starting point for any user interaction with the remote lab system. A user must be logged in via Azure AD in order to see the schedule and device list. A logged in user may book a device for a single hour and may view any device. If a viewed device is booked for the current hour then the user will be given options to program and control the device. Programming and controlling specifics are device dependent and are configured for the device through a driver.

Repository

remote-lab

Install Location

Remote Lab Server - Apache

Framework

Angular

Remote Services

Remote services are provided for schedule management (create and delete schedules for a specific device) and device registration. The remote service application also leverages the blackbox-discovery module to announce the remote lab agent's presence and to discover other agents. Discovered agents advertising an embeddedDevice will automatically have their device registered.

Repository

ellipsis-embedded

Install Location

Remote Lab Server - Node.js application

Framework

Node.js, Open API

Device Driver

A device driver is implemented as a javascript file containing info, onStart and onStop functions. Driver details are specified in a configuration file. For further details see Driver development.

Repository

Driver specific

Install Location

Remote Lab Server

Framework

Node.js

Deployment

This section relates to the setup of a Raspberry Pi (or similar computer) with all required backend services. If you are using a Remote Lab SD card image on a Raspberry Pi (recommended) then only the following are needed:

Note that you will need an internet connection for many of the below commands.

SD Card Setup with Raspberry Pi OS

Assuming you are building a remote lab on a Raspberry Pi you have two options:

  1. Flash a remote lab image onto an SD card, or
  2. Flash a Raspberry Pi OS image onto the SD card and manually install all services.

Option 1 is the easiest option and is recommended. You can use a tool such as Balena Etcher to flash the image which can be found here: TODO.

If you are deploying from a Raspberry Pi OS image then you can either use the Raspberry Pi Imager or download an image and use a tool such as Balena Etcher to flash the image. Some useful instruction on setting up a Pi image including SSH and Wifi can be found here. You will then need to continue to follow all steps below in this section to complete the Raspberry Pi software setup.

All remote lab specific configuration is in the blackbox directory, so that should be created first:

sudo mkdir /opt/blackbox
sudo chown pi /opt/blackbox

Apache Deployment

Apache Web Server is used for browser based user interface hosting including the Remote Lab UI. Install Apache2 on a Raspberry Pi as follows:

sudo apt-get update
sudo apt-get upgrade
sudo apt install apache2 openssl -y

The Remote Lab UI is the starting point for a user to interact with a remote lab. As such it must be the default page when browsing to the remote lab and is therefore deployed to Apache's default root /var/www/html/. Deploy the Remote Lab UI by executing the following commands on the remote lab computer (you may either run each line one by one or copy the below into a script file):

curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/index.html --output /var/www/html/index.html
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/facicon.ico --output /var/www/html/favicon.ico
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/main.js --output /var/www/html/main.js
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/main.js.map --output /var/www/html/main.js.map
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/polyfills.js --output /var/www/html/polyfills.js
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/polyfills.js.map --output /var/www/html/polyfills.js.map
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/runtime.js --output /var/www/html/runtime.js
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/runtime.js.map --output /var/www/html/runtime.js.map
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/styles.css --output /var/www/html/styles.css
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/styles.css.map --output /var/www/html/styles.css.map
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/vendor.js --output /var/www/html/vendor.js
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/vendor.js.map --output /var/www/html/vendor.js.map
curl https://bitbucket.org/ellipsistechnology/remote-lab/raw/4356afcdf41bc81201c7fe20f812424e3f4b3949/dist/remote-lab/config.json --output /var/www/html/config.json

Apache will need to serve via HTTPS. To enable HTTPS:

sudo a2enmod ssl
sudo a2enmod rewrite
Then edit /etc/apache2/apache2.conf and add the following at its end:
<Directory /var/www/html>
    AllowOverride ALL
</Directory>

You will need to generate a certificate for which we can use openssl installed previously. First, create a directory to store the certificate:

mkdir /opt/blackbox/certs
cd /opt/blackbox/certs
Then create the certificate:
sudo openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out blackbox.crt -keyout blackbox.key
Fill in the answers as prompted, e.g.:
Generating a RSA private key
.......++++
............................................++++
writing new private key to 'blackbox.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:Tas
Locality Name (eg, city) []:Hobart
Organization Name (eg, company) [Internet Widgits Pty Ltd]:UTas
Organizational Unit Name (eg, section) []:Engineering
Common Name (e.g. server FQDN or YOUR name) []:2c1c9a75-d658-4fe5-80f7-fe7a39c3e360
Email Address []:

The certificates will then need to be configured in Apache. Edit /etc/apache2/sites-enabled/000-default.conf and add the following:

<VirtualHost *:443>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    SSLEngine on
    SSLCertificateFile /opt/blackbox/certs/blackbox.crt
    SSLCertificateKeyFile /opt/blackbox/certs/blackbox.key
</VirtualHost>
In the port 80 VirtualHost add the following to redirect users to use HTTPS:
    RewriteEngine on
    RewriteCond %{HTTPS} !=on
    RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R=301,L]

Restart apache and we're ready to test:

sudo service apache2 restart

Open the Remote Lab in your browser using it's IP address. Note that if you are using a self signed certificate as per the above instructions, then you will need to import the certificate and trust it before your browser will allow you to see the page.

If all works correctly you should be redirected to HTTPS, then redirected to login with Azure AD. For the Azure AD login to succeed you will need to follow the below instructions. Once logged in you will need the Remote Services installed and running through the Node.js Deployment before you can interact with any devices.

Azure AD App Registration

User login is managed through Azure AD. You will need to register the remote lab application with Azure in order to allow users to log in:

  1. Open your Azure AD portal via https://portal.azure.com/.
  2. Navigate to Azure AD Security: Click on All Services, then Identity, then Azure Active Directory.
  3. Click on App registrations.
  4. Click on New registration.
  5. Fill in requested details as required, making sure that the Redirect URI is set to the specific remote lab address.

Once you have registered the application you will need to take note of the Application (client) ID and copy it into the config file:

  1. On the Raspberry Pi, open /var/www/html/config.json for editing.
  2. Enter the Application ID into authentication/appID, save and close the file.

Authentication should now automatically log you in when you navigate to the Remote Lab web page.

Node.js Deployment

The Remote Services used for managing devices and schedules are web servers running on Node.js. Install Node.js on the Raspberry Pi as follows (make sure you are in a temporary directory such as ~/tmp):

wget https://nodejs.org/download/release/v14.3.0/node-v14.3.0-linux-armv7l.tar.gz
tar -xzf node-v14.3.0-linux-armv7l.tar.gz
cd node-v14.3.0-linux-armv7l/
sudo cp -R * /usr/local/
To check that the install was successful, run the following command:
node -v
The output should be v14.3.0.

To install the Remote Services execute the following commands:

cd /opt/blackbox
mkdir ellipsis-embedded
cd ellipsis-embedded
curl https://bitbucket.org/ellipsistechnology/ellipsis-embedded/raw/bbc86a9a184935d8822f33ab77f6f2d884dd53be/prod/ellipsis-embedded-services_0.0.1.zip --output ellipsis-embedded.zip
unzip ellipsis-embedded.zip
rm ellipsis-embedded.zip

The remote services should run on startup and run as a background task. To set that up create /lib/systemd/system/remotelab.service and add the following to it:

[Unit]
Description=Remote Lab
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/local/bin/node /opt/blackbox/ellipsis-embedded/index.js

[Install]
WantedBy=multi-user.target
Set the service file's permissions:
sudo chmod 644 /lib/systemd/system/remotelab.service
Then make the service run during boot:
sudo systemctl daemon-reload
sudo systemctl enable remotelab.service
sudo reboot

If you have any issues at this point (e.g. the service is not running) you can check by running

sudo systemctl list-units --failed
and if it has failed view the log with
journalctl -u remotelab.service -b

Note that the service will be running on port 8100 over HTTP by default. You will need to configure TLS in the Agent Configuration file. You can reuse the certificates created when setting up Apache above; i.e. /opt/blackbox/certs/blackbox.crt and /opt/blackbox/certs/blackbox.key.

Camera Setup

https://www.ics.com/blog/raspberry-pi-camera-module#.VJFhbyvF-b8

Motion - also not so good

ref. https://raspberry-valley.azurewebsites.net/Streaming-Video-with-Motion/

Old Process - FAILED

sudo apt-get install git

sudo apt-get install cmake

cd ~
git clone http://github.com/raspberrypi/userland
./buildme

Proxy

https://raspberrypi.stackexchange.com/questions/68580/how-do-i-set-proxy-in-raspberry-pi-raspbian-os-or-any-linux-using-command-li/68583

Agent Configuration

The ellipsis-embedded project is configured via /etc/blackbox-config.json which MUST have the following format:

{
    "agent": {
      "name": "<agent name>",
      "port": "<service port>",
      "tls": {
        "key": "<file path>",
        "cert": "<file path>",
        "ca": "<file path, optional>"
      }
    },
    "devices": [
          {
              "name": "<device name>",
              "status": "<'stopped', 'starting' or 'running'>",
              "type": "<device type; e.g. basys3>",
              "programmer": {
                  "type": "<programmer type; e.g. xvc>"
              },
              "location": "<'local' or 'remote'>",
              "driver": {
                  "path": "<path to js file>",
                  "onStart": "<start function name>",
                  "onStop": "<stop function name>",
                  "getInfo": "<info function name>"
              }
          }
      ]
}

The default port is 8100 and the agent will be polled on this port for device information. If the tls property is present then the ellipsis-embedded server will use HTTPS, otherwise is will default to HTTP.

Currently, most device fields are for information only and are not used except for display to a user. The device driver setup is used by Remote Services to interact with device specific software. For more information on setting up a driver for a custom device, refer to Driver Development.

Driver Development

Devices are configured through a driver object in the agent configuration. A device driver is used by Remote Services to interact with device specific software. There are three points at which the driver functions will be called: Device registration, device start, device stop. The start and stop events are triggered by the schedule managed by the Remote Services.

A driver is simply a Node.js compatible Javascript file that includes the following 3 functions:

  • getInfo({req}) Returns a JSON object with device information.
  • onStart({owner}) Triggered when the device schedule starts.
  • onStop() Triggered when the device schedule ends.

Driver Functions

getInfo(context)

The getInfo function provides general information specific to the device. It MAY be given a different name by specifying the driver getInfo property in the Configuration file.

Parameters

context

A JSON object including contextural information relating to the origins of the information request. Currently this only contains a req member that is the HTTP request object.

Return Value

The return value MUST be either a JSON object containing device information or a Promise to return such an object. These details will be displayed to the user through the Remote Lab UI when a specific device is selected. Additionally, programUrl and controlUrl string properties MAY be added to the returned object which will be used to embed a program and a control user interface in the Remote Lab UI when a specific device is selected.

onStart(state)

The onStart function is called when a device start event is triggered by the commencement of a schedule. Note that this may be triggered at any time during a schedule and may be triggered multiple times. It MAY be given a different name by specifying the driver onStart property in the Configuration file.

Parameters

state

A JSON object that includes device state information. Currently this only contains an owner member that is a string specifying the owner's credentials. The owner of a device is the user owning the current booking if an active booking exists, otherwise undefined will be returned.

Return Value

The return value MUST be either undefined or a Promise that resolves when the start procedure is complete.

onStop()

The onStop function is called when a device stop event is triggered by the ending of a schedule. It MAY be given a different name by specifying the driver onStop property in the Configuration file.

Return Value

The return value MUST be either undefined or a Promise that resolves when the stop procedure is complete.

Agent Discovery

The discovery service MUST be compatible with the blackbox-discovery protocol. This simply broadcasts messages on port 5555 and listens for broadcasts from agents on the same port.

Messages have the following format:

BB ANNOUNCE
<Agent Info>
where <Agent Info> is a JSON object conforming to the following definition:
{
    name: string
    path: string
    port: number
    address?: string
    protocol?: string
}
  • name The agent's name used for identification.
  • path The agent's ReST API root path.
  • port The agent's ReST API port.
  • address The agent's ReST API address.
  • protocol The agent's ReST API protocol.
The agent MUST be a blackbox agent according to the Blackbox Specification. The name and port will be read from the Blackbox Config file if using the ellipsis-embedded project.

If using the ellipsis-embedded project then these announcements will begin automatically when the service is started and a Blackbox ReST API will be made available. If using blackbox-discovery directly (without ellipsis-embedded) you will need to create an Agent and start announcing:

import {Agent} from 'blackbox-discovery'
...
const agent = new Agent({
    name: 'some name',
    path: '/',
    port: 1234,
    protocol: 'http'
})
agent.startAnnouncing()

If you need to listen for newly discovered agents you can push a new callback function onto the agent's listeners:

agent.listeners.push((discoveredAgent) => {
    /* Do stuff. */
})