NEPI Engine – Link Communications Interface
Introduction
This document provides details on the NEPI Link communication interface, which provides authentication and communications between NEPI Engine software running on an edge-compute device and NEPI Connect software running on a remote server or Cloud platform , which supports a variety of connectivity gateways (Ethernet, WiFi, cellular, RF, acoustic…) and data structures (two-way messaging, data and software file transfer, and live remote RUI and SSH access).
Software Components
The NEPI Link remote system interface solution is comprised of both device-side and server-side software components that provide device to server authentication, secure two-way datagram messaging, secure two-way file transfer, and secure internet connections to NEPI enabled devices.
Component | Subcomponents | Host | Description |
CONNECT | NEPI-BOT | Device | Device-side application that facilitates CONNECT communications, services, and interfacing. |
NEPI-EDGE-SDK | Device | SDK used to convert device side data into NEPI CONNECT message formats and initiating NEPI-BOT connections | |
NEPI-SERVER | Server | Server-side application that facilitate CONNECT communications, services, and interfacing. |
NEPI-BOT
NEPI-BOT is a lightweight Linux Python application that runs on an Edge device providing secure Edge-to-Cloud (or Remote server) messaging, data, and software transport over a variety of low and high-bandwidth communications gateways. Provides both python and c+ APIs for creating and transferring data over the NEPI LB and HB links. The NEPI-BOT application is included as part of Numurus’ NEPI Engine device-side software toolkit for robotic and AI automation applications.
NEPI-EDGE-SDK
NEPI-EDGE-SDK is a lightweight Linux C++ SDK application that compiles and runs on an Edge device providing tools for converting device side data into NEPI-BOT message formats. The SDK also has tools for configuring and initiating NEPI-BOT connections to remote NEPI-SERVER applications running on remote PCs, servers, and Cloud platforms.
NEPI-SERVER
NEPI-SERVER is a containerized server-side application for communicating with NEPI-BOT over a variety of communication gateways from a remote server. Provides a server-side publish-subscribe interface for transferring data, configurations, a software between devices and server.
NEPI-BOT to NEPI-SERVER Interface
NEPI device to server communication capabilities are broadly divided into three classes: Low-bandwidth (LB), High-bandwidth (HB), and Persistent Link (PL). The distinction between these capability sets is generally based on the size and frequency of the transfer contents, not necessarily on the relative throughput capabilities of the physical communication media, though the need for an LB class of capabilities is, indeed, inspired by limited bandwidth communication channels, where HB/PL capabilities are only supported when designated high-throughput communication channels are available.
Device Authentication and Messaging System
NEPI is designed to provide secure authentication and encrypted transfer of data for both LB and HB connections to ensure CUI compliance. Additionally, NEPI provides configuration options to facilitate secure, encrypted data-at-rest.
All NEPI-encrypted transfers and data-at-rest encryption methods adhere to NIST FIPS 140-3 “Security Requirements for Cryptographic Modules.” Additionally, HB-transferred files may be pre-encrypted by customer software before passing these files to NEPI (either DT or DO). In that case, the customer layer of encryption must be decrypted by the customer receiving software.
The NEPI-BOT – NEPI-SERVER interface adheres to a server/client architecture, where NEPI-SERVER acts as the server and authenticates all connections according to RSA key-based authentication. Furthermore, NEPI-SERVER authentication limits connections to devices from a whitelisted set of domains/IP addresses, all in accordance with standard CUI compliance guidelines.
NEPI-CONNECT communications are initiated by the device and passed to NEPI-SERVER by NEPI-BOT. This direction of the interface is referred to as Device Originated (DO). Traffic that is initiated by the server, on the other hand, is referred to as Device Terminated (DT).
PL-Link Interface (Coming Soon)
NEPI’s PL-Link capabilities are designed for low-latency data and control streaming between edge-devices and remote-services. The PL link connects the edge-device’s Resident User Interface (RUI) and supporting micro web services (MWS) that publish available data and accept control inputs connected to a NEPI device’s browser-based Resident User Interface (RUI). After each successful connection by a device to the server, the devices internet IP address is updated in the device’s Link database and made available to server-side connected applications.
HB-Link Interface
NEPI’s HB component operates somewhat differently than LB component in that in general HB capabilities are more transparent and require less custom coordination from/to the device. HB capabilities provide unrestricted automated “on demand” two-way file transfers. Some typical use-cases for the HB component are pre-mission software update and large-scale parameter reconfiguration, post-mission data retrieval, complete device software back-ups. HB transfers are generally restricted to times when the device is near enough the server to allow WiFi or Ethernet connections between the two, as most HB capabilities rely on services that run over internet protocols (IP).
HB-Clone
HB-Clone provides a transparent filesystem cloning mechanism between server and device.
The device populates a filesystem node with arbitrary contents, and upon a subsequent HB connection between NEPI-BOT and NEPI-SERVER, these contents are transferred automatically. There is no restriction on the contents that can be transferred besides respecting the raw disk capacity of the device when populating the filesystem node. This flexibility allows HB-Clone to provide the framework for capabilities such as
– Device data offload
– Complete device configuration capture
– Large-scale configuration update
– Device s/w updates
– Complete device digital cloning
Separate filesystem nodes are provided for to-device and from-device transfers, but otherwise contents must be interpreted by the destination (device or server’s) s/w.
LB-Link Interface
The LB capabilities are designed to facilitate secure in-situ communications and coordination between the device and a NEPI-SERVER instance. Many physical communication channels between deployed devices and a local or cloud-based server are bandwidth-limited due to physical, power, and cost limitations. Examples include satellite communications links and underwater acoustic modems. NEPI-BOT provides a configurable ordered list of LB channel types, and these channel connections are attempted in the configured order at the start of each LB session. Individual channels have individual configurable parameters controlling, for example, maximum attempts and maximum throughput per session.
Individual LB capabilities include in-situ modification of NEPI-specific parameters and device parameters (LB-CONFIG), general data snippets and snapshots (LB-DATA), general device state and status (LB-STATUS) and small-payload generic messages (LB-GENERAL) that can be tailored to the needs of the application. LB capabilities are implemented via a small set of topics, each of which serves a particular purpose, where each topic defines a message type that includes both specified and unspecified (raw/free) data contents. Individual NEPI LB capabilities are categorized as topics, where a topic consists of a well-defined message format. Most topics are specifically DT or DO, although the LB-General topic is bi-directional as described below. The messages consist of fields, each of which has a proscribed set of data types (often just a single data type) and a proscribed resolution. All fields are optional, though the absence of some fields renders the message indecipherable to NEPI.
LB-Status Topic
The LB-Status topic provides DO high-level state and status relevant to most sensor and robotic applications and is the essential component to asset management in a NEPI-enabled system. As such, each LB connection results in at least one LB-Status message (with more possible as described in LB-Data Topic.
Table 2: LB-Status Message contents
Field Name | Precision (data type) | Description/Notes |
timestamp | Millisecond (RFC3339 String) | Indicates a baseline timestamp. Used as a reference point for the navsat fix timestamp and any corresponding LB-Data timestamps. |
navsat_fix_time_offset | Millisecond (int32) | Positive(before)/Negative(after) offset from timestamp |
Degree (float) | Positive is north of equator | |
longitude | Degree (float) | Positive is east of prime meridian |
heading | Millidegree (uint32) | |
heading_ref | North/Magnetic (enum) | True North =1 , Magnetic North = 0 |
roll_angle | Millidegree (int32) | Device-defined reference axis |
pitch_angle | Millidegrees (int32) | Device-defined reference axis |
temperature | Decidegrees (int32) | Main temperature, degrees C |
power_state | 1% increments; 0 – 100 (uint) | Power state (e.g., battery charge remaining (percentage)), device-specified |
device_status | Undefined (byte array) | Raw buffer for additional device-defined status |
LB-Data Topic
The LB-Data topic provides a mechanism for transferring small data sets or data snippets. Each LB-Data message corresponds to exactly one LB-Status message and refers to that LB-Status message through their shared sys_status_id field. The Status-to-Data relationship is one-to-many, so a single LB-Status message may be associated with many LB-Data messages. NEPI guarantees that a LB-Data message will never be transmitted ahead of the corresponding LB-Status message, but the reverse is not necessarily true. See the discussion in LB Message Prioritization (PIPO) for additional details.
Table 3 — LB Data Message Contents
Element Name | Precision (data type) | Description/Notes |
type | Unrestricted (string) | Indicates the type or source of data. Limited to exactly 3 characters. |
instance | (uint32) | To differentiate between data generated by the same source |
data_time_offset | Millisecond (int32) | Positive(after)/negative(before) offset from corresponding LB-Status message’s timestamp field. |
latitiude_offset | Degree (float) | Offset from corresponding LB-Status message’s latitude field |
longitude_offset | Degree (float) | Offset from corresponding LB-Status message’s longitude field |
heading_offset | Millidegree (int32) | Offset from corresponding LB-Status message’s heading field |
roll_offset | Millidegree (int32) | Offset from corresponding LB-Status message’s roll field |
pitch_offset | Millidegree (int32) | Offset from corresponding LB-Status message’s pitch field |
payload | Undefined (byte array) | Raw data buffer |
LB-Config Topic
The LB-Config topic provides a mechanism for transferring one or more parameter value updates from server to device. These include parameters that control the behavior of NEPI-Bot, itself, as well as parameter values that are passed to the device.
The LB-Config message contains one or more config updates for the specified subsystem as detailed in the table below.
Table 4 — LB-Config Message Contents
Element Name | Precision (data type) | Description/Notes |
comm_index | Incrementing counter (uint32) | See Communication Index |
subsystem | NEPI/Device (enum) | Identifies whether this config parameter is for NEPI-Bot or the device |
identifier | (string) | Identifies the parameter. Can be a string or numeric identifier. |
(uint32) | ||
value | (double) | Parameter value. Can be any one of the specified data types. |
(float) | ||
(int64) | ||
(uint64) | ||
(bool) | ||
(string) | ||
(byte array) | ||
(One or more identifier/value pairs) |
LB-General
The LB-General topic provides a catch-all for DT and DO messages that are not well-represented by one of the other LB topics. This includes, but is not limited to, commands, requests, responses, and alerts. As a generic messaging system, much of the design and interpretation of the message contents is left to the device engineer.
Like the LB-Config topic, LB-General may be routed to/from NEPI-Bot directly or passed through to/from the device. In contrast with the LB-Config topic, however, each LB-General message may contain only one message identifier and optional payload.
Table 5 — LB-General Message Contents
Element Name | Precision (data type) | Description/Notes |
comm_index | Incrementing counter (uint32) | See Communication Index |
subsystem | NEPI/Device (enum) | Identifies whether this message is from (DO) or to (DT) NEPI-Bot or the device |
identifier | (string) | Identifies the message type. Can be a string or numeric identifier. |
(uint32) | ||
payload | (double) | Payload value. Can be any one of the specified data types. |
(float) | ||
(int64) | ||
(uint64) | ||
(bool) | ||
(string) | ||
(byte array) |
LB Comm Index
NEPI natively supports a simple, primitive state and communication coordination mechanism. Many LB messages include a comm_index field, representing a generic communication index. The communication index provides a means by which responses to requests, and config. updates on the LB channels can be matched with the initial request or config. update that generated them.
There is no NEPI-enforced policy for these comm_index fields. Rather, the comm_index is a convenience that may be employed by device engineers to correlate DT and DO traffic.
LB Message Prioritization (PIPO)
Given the limited throughput and duty cycle of LB links, it is possible to generate a backlog of LB messages. NEPI-BOT employs a running prioritization scheme, PIPO (Priority-In Priority-Out), to determine which messages from which LB topics will be transmitted during a given LB session.
For a given session, NEPI-BOT attempts to transmit DO LB messages according to the following prioritized list:
1) The most recent LB-Status message
2) All pending LB-General messages in FIFO order
3) PIPO-prioritized LB-Data and any as-yet-untransmitted LB-Status messages corresponding to that data.
The Server maintains a database of previously transmitted LB-Status messages, so LB-Data messages that are associated with an LB-Status message that has already been transmitted do not incur the overhead of that LB-Status message.
The PIPO system employs a tunable algorithm for assigning priority scores to LB-Data messages. The score components include the following:
Table 6 — PIPO Score Components
Component | Description | Static/dynamic |
quality | Device assigned per LB-Data message. Allows deprioritizing data deemed low-quality by the device data collection/processing routines. | Static |
type | Device assigned per data source. Allows deprioritizing data from sources deemed faulty or of lower mission value. | Static |
event | Device assigned per collection/wake event. Allows deprioritizing event-driven data for events of lower interest | Static |
size | NEPI-computed wire protocol size as a percentage of configurable per-link-type max size | Static, but varying for each LB link type |
age | NEPI-computed age of data in (fractional) days. Allows deprioritizing older data. | Dynamic – recomputed for each session |
The PIPO Score Components are all normalized to the range [0.0,1.0], except the Age score which increases monotonically without limit. Each score component is individually weighted by a configurable parameter, allowing device engineers to tailor the algorithm for their specific application. The general PIPO scoring function is implemented as:
Score = (A*quality + B*type + C*event + D_link*size) / (1 + E*age).
Where {A,B,C,D_link,E} is the set of weighting parameters (D_link individually parameterized for each of the configured LB link types).
PIPO also employs a configurable purge score, and any LB-Data message whose PIPO score falls below this purge score (for all configured LB link types) is deleted. In particular, unless the Age weighting, E, is set to 0, all data’s PIPO score monotonically decreases with time and will eventually fall below the purge threshold.
Error Detection and Correction
NEPI does not provide a robust built-in link/transfer error detection capability, particularly for LB capabilities. It is helpful to view NEPI-LB as analogous to a UDP/IP service, where additional error detection and integrity checks can be implemented at the device level by leveraging the unspecified-content components of various LB message types.
NEPI-Bot (Device-Side Application)
NEPI-Bot Installation
NEPI-Bot is a python-based application for managing authentication and data transfer between edge-devices and remote servers (PC, Server, Cloud…). NEPI-Bot edge-device installation instructions are provided in the top-level Readme file of the NEPI-Bot source-code repo.
NEPI-BOT Configuration
NEPI-Bot is highly configurable, with configuration parameters separated into two general classes; those that can be modified in-situ via the NEPI-CONFIG topic and those that cannot. Examples of the former include logging controls, data throughput limits, and data prioritization controls (see LB Message Prioritization (PIPO)). Examples of the latter include hardware link-type specification and priority ordering of the link types.
A description of the complete set of NEPI-BOT configuration parameters is beyond the scope of this document. See the NEPI Configuration Guide (coming soon) for additional details.
NEPI-BOT Interfacing Options
The NEPI-BOT application can either run as an application on a customer Linux processor and communications gateways, or on a secondary external IoT board with integrated communications hardware. These two options are illustrated in Figure 1 below.
Figure 1: NEPI-BOT Software Deployment Options
Several NEPI-BOT software interface options are available: 1) through standard ROS topics and services leveraging the integrated ROS-NEPI-BOT Node, 2) NEPI-BOT software calls, and 3) a NEPI-BOT Linux SDK. These options are illustrated in Figure 2 below. For more details on any of these options please refer to the appropriate ICD from the references section at the beginning of this document.
Figure 2: NEPI-BOT Software Interface Options
The NEPI-Bot-SDK Linux installation and use instructions are provided in the top-level Readme file of the NEPI-Edge-SDK source-code repo. An example NEPI-BOT-SDK interface script is included in Appendix A of this document.
NEPI-Server (Server-Side Application)
Installation
The NEPI-Server application runs in a docker container on a remote internet connected system (PC, Server, Cloud…). NEPI-Server installation instructions are provided in the top-level Readme file of the NEPI-Server source-code repo.
Appendix A – NEPI-EDGE-SDK Python Script Example
#!/usr/bin/env python3
import os
import time
from nepi_edge_sdk import *
# Helper for config and general message params
def printParam(param_id, param_val):
id_string = None
val_string = None
if (isinstance(param_id, bytes) or isinstance(param_id, bytearray)):
id_string = param_id.decode('utf-8')
else:
id_string = str(param_id)
print("\t\tID (" + str(type(param_id)) + "): " + id_string)
if (type(param_val) != list):
print("\t\tVal (" + str(type(param_val)) + "): " + str(param_val))
else:
print("\t\tVal (" + str(type(param_val)) + "): " + str(len(param_val)) + " entries")
print ('\t\t[{}]'.format(', '.join(hex(x) for x in param_val)))
if __name__ == "__main__":
print("Testing NEPIEdgeSdk (python bindings)")
sdk = NEPIEdgeSDK()
# First, set the the NEPI-EDGE filesys interface path.
sdk.setBotBaseFilePath("./nepi_sdk_example_filesys")
# Now we can report the NUID for this instance
nuid = sdk.getBotNUID();
print("Detected NUID: " + nuid);
# Now create the status
status = NEPIEdgeLBStatus("2020-09-03 17:14:25.2-04:00")
# Populate some of the optional fields
status.setOptionalFields(navsat_fix_time_rfc3339="2020-08-21T09:49:00.0-04:00", latitude_deg=47.6062, longitude_deg=-122.3321,
heading_ref=NEPI_EDGE_HEADING_REF_MAG_NORTH, heading_deg=45.0,
roll_angle_deg=5.5, pitch_angle_deg=-17.6, temperature_c=40.0,
power_state_percentage=94.2, device_status=bytearray([1,2,3,4,5]))
# Next create a couple of data snippets
data_snippet_1 = NEPIEdgeLBDataSnippet(b'cls', 0)
# And populate some of the optional fields
data_snippet_1.setOptionalFields(data_timestamp_rfc3339="2020-09-03 17:14:25.6-04:00", latitude_deg=47.607, longitude_deg=-122.33,
heading_deg=22.5, roll_angle_deg=6.5, pitch_angle_deg=-18.2,
quality_score=0.33, type_score=1.0, event_score=0.5, data_file="./example_files/left_img_detections.txt")
# Now the second data snippet -- empty except for the required type and instance fields and optional data file field
data_snippet_2 = NEPIEdgeLBDataSnippet(b'cls', 1)
data_snippet_2.setOptionalFields(data_file="./example_files/left_img_small.jpeg")
# And export the status+data
status.export([data_snippet_1, data_snippet_2])
print("Created status and two associated data snippet files");
# Now create a general message
general_1 = NEPIEdgeLBGeneral()
# And populate its sole payload field -- set it up for a string-keyed, floating-point valued payload
general_1.setPayload("WantSomePI?", 3.14159)
# And export it to a file for NEPI-BOT to consume
general_1.export()
print("Created a DO General file")
# Let's do another general message
general_2 = NEPIEdgeLBGeneral()
# Populate this one with a numerical key and an arbitrary byte array for its value
general_2.setPayload(12345, bytearray([0xDE, 0xAD, 0xBE, 0xEF]))
# And export it to a file
general_2.export()
print("Created another DO General file")
# Now we demonstrate import -- First a collection of Config messages */
cfg_list = NEPIEdgeLBConfig.importAll(sdk)
print("Imported " + str(len(cfg_list)) + " Config messages")
for i,cfg_msg in enumerate(cfg_list):
param_count = cfg_msg.getParamCount()
print("Config message " + str(i) + " has " + str(param_count) + " parameters")
for j in range(param_count):
print("\tParam " + str(j) + ":")
(param_id, param_val) = cfg_msg.getParam(j)
printParam(param_id, param_val)
general_dt_list = NEPIEdgeLBGeneral.importAll(sdk)
print("Imported " + str(len(general_dt_list)) + " General-DT messages")
for i, general_dt_msg in enumerate(general_dt_list):
print("\tGeneral-DT Msg " + str(i) + ' Param:')
(param_id, param_val) = general_dt_msg.getParam()
printParam(param_id, param_val)
# Now some HB testing. Setup data export.
# First, create a temporary/testing data folder
test_directory = os.getcwd() + '/testing_data'
if not os.path.exists(test_directory):
os.makedirs(test_directory)
with open(test_directory + '/testfile.txt', 'w') as f:
f.write('This is a nepi_sdk_example_session testfile -- you can delete it and the parent directory')
# Now link the test directory to NEPI for HB data export
sdk.linkHBDataFolder(test_directory)
print("Linked HB Data Offload folder to " + test_directory)
# Now launch the Bot process and wait for it to terminate
run_lb = True
lb_timeout_s = 30
run_hb = True
hb_timeout_s = 30
print('Starting BOT')
print('\tLB is ' + ('Enabled' if run_lb is True else 'Disabled') + ', LB Timeout is ' + str(lb_timeout_s) + ' seconds')
print('\tHB is ' + ('Enabled' if run_hb is True else 'Disabled') + ', HB Timeout is ' + str(hb_timeout_s) + ' seconds')
sdk.startBot(run_lb, lb_timeout_s, run_hb, hb_timeout_s)
bot_kill_timer = 0
while (sdk.checkBotRunning() is True):
bot_kill_timer += 1
print(str(bot_kill_timer))
time.sleep(1)
# Here is how to kill BOT before it terminates on its own -- this is an error-path fallback for a hung NEPI-BOT process, not a part
# of normal execution... BOT _should_ manage its own timeouts properly
if (hb_timeout_s + 1 == bot_kill_timer):
print('Signalling BOT to shut down gracefully')
sdk.stopBot(0) # Soft kill
elif (hb_timeout_s + 5 == bot_kill_timer):
print('Killing BOT forecefully')
sdk.stopBot(1)
# Optional -- you can unlink the HB Data Folder if you want, or just leave it linked
sdk.unlinkHBDataFolder()
print('Unlinked HB Data Folder')
# Now import the execution status to get information about how it went
exec_status = NEPIEdgeExecStatus()
(lb_statuses, hb_statuses, software_was_updated) = exec_status.importStatus()
print('Imported the BOT Execution Status')
if ((0 == len(lb_statuses)) and (run_lb is True)):
print('\tLB connection attempt failed - No status to report');
for i,status in enumerate(lb_statuses):
print('\tLB Connection Status ' + str(i) + ':')
print('\t\tComms Type: ' + str(status['comms_type']))
print('\t\tComms Status: ' + str(status['comms_status']))
print('\t\tStart Time: ' + str(status['start_time_rfc3339']))
print('\t\tStop Time: ' + str(status['stop_time_rfc3339']))
print('\t\tWarnings:')
for j,warning in enumerate(status['warnings']):
print('\t\t ' + str(j) + '. ' + str(warning))
print('\t\tErrors:')
for j,error in enumerate(status['errors']):
print('\t\t ' + str(j) + '. ' + str(error))
print('\t\tMessages Sent: ' + str(status['msgs_sent']))
print('\t\tPackets Sent: ' + str(status['pkts_sent']))
print('\t\tMessages Received: ' + str(status['msgs_rcvd']))
if ((0 == len(hb_statuses)) and (run_hb is True)):
print('\tHB connection attempt failed - No status to report');
for i, status in enumerate(hb_statuses):
print('\tHB Connection Status ' + str(i) + ':')
print('\t\tComms Type: ' + str(status['comms_type']))
print('\t\tComms Status: ' + str(status['comms_status']))
print('\t\tStart Time: ' + str(status['start_time_rfc3339']))
print('\t\tStop Time: ' + str(status['stop_time_rfc3339']))
print('\t\tWarnings:')
for j,warning in enumerate(status['warnings']):
print('\t\t ' + str(j) + '. ' + str(warning))
print('\t\tErrors:')
for j,error in enumerate(status['errors']):
print('\t\t ' + str(j) + '. ' + str(error))
print('\t\tDirection: ' + str(status['direction']))
print('\t\tData Sent: ' + str(status['datasent_kB']) + 'kB')
print('\t\tData Received: ' + str(status['datareceived_kB']) + 'kB')
if software_was_updated:
print('\tSoftware Was Updated!')
else:
print('\tSoftware Not Updated')