Holoscan Sensor Bridge v2.5.0

HSB Emulator

The holoscan-sensor-bridge (HSB) repository includes a minimally featured emulation of the HSB framework’s IP for use in testing and development within a hosted environment - the “HSB Emulator”. Though it is called like a single object, it is a collection of objects that can be used to configure a real endpoint and data source for HSB host-side applications. This allows for simulation, testing, or development of a host application when the hardware is not fully developed, in testing, or a full software-in-loop/CI environment.

An outline of the typical communication pipelines between and HSB Host application and an HSB Emulator application is shown in the diagram below. This graphic is but one particular topology and user client code may piece together the components so that an HSB Emulator application may meet the needs of their particular Host application(s)’s code. Note also that the two applications may exist either on different device, within the same device in different processes, or even within different threads of the same process (through transport options may be limited). The details of the specific objects in the diagram are described in Developing with HSB Emulator

HSBEmulatorApplicationCommunicationFlow.png

The HSB Emulator as provided is not meant to be a full implementation of an MCU or FPGA HSB device nor provide the full performance benefits of those implementations. Many features of the HSB IP are not fully implemented in that the existing emulator will accept and reply to requests, e.g. ptp, but may be “no-ops” internally so that apps that currently depend on that interface being present can proceed but leave the implementation detail to applications or expected behavior. The features that are implemented are only the ones necessary for a host application to configure and accept sensor data through the HSB transport pipelines.

Features

The features that are supported in provided code are

  1. Sensor agnostic

    • HSB Emulator itself has no built-in knowledge of any given sensor.

    • Sensors and drivers that do not depend on state change queries, e.g. the HSB example “stateless” imx drivers, are compatible by default

  2. LinuxTransmitter for RoCEv2 UDP-based transport

    • Name is to be consistent with the rest of HSB where the implementation is via Linux interface access and sockets

  3. COETransmitter for IEEE 1722B link layer transport (Camera-over-Ethernet, CoE)

  4. BaseTransmitter and DataPlane interfaces for adding experimental transport protocols

  5. Underlying C++ implementation + Python API

  6. CUDA buffer support

    • DLPack array interface format - compatible with contiguous arrays from any Python package that supports DLPack

    • sensor data buffers may be provided on host or CUDA GPU device

  7. Configuration of “stateful” sensors via an I2CPeripheral interface

    • SPI and GPIO are not fully implemented

  8. Isolated build from host

    • HSB Emulator may be built without linking to either HSB or Holoscan SDK to minimize environmental and package dependencies

  9. Loopback testing ability

    • The HSB Emulator can be used to test both accelerated and unaccelerated transport on the same device the host is running on though for accelerated transport, e.g. RoceReceiverOp and SIPLCaptureOp, a physical connection and network isolation would be needed

Limitations

The HSB Emulator is meant to be as hardware agnostic as possible and may run on the same device as host applications where it cannot change many settings. To that end, it is not fully emulating any particular FPGA or MCU implementation of HSB IP and therefore some features are not or will not be implemented.

  1. Only one instance of HSBEmulator is likely to work on any single device:

    • This is a current limitation in the APIs to be consistent with real HSB IP. While only one HSBEmulator may run, the number of sensor DataPlanes that may attach to the instance is limited only by the number of IP addresses that can be assigned and configurable sensor interfaces (SIFs) for the emulated device (<= 32).

    • Running in isolated network namespaces will free up this restriction for connections other than a SW loopback, but requires application & system maintenance

  2. The HSB Emulator may not be reprogrammed via the host side IP tools like a real HSB:

    • set_ip

    • p2p

    • reset

    • any reprogramming

Environment Compatibility

Python and C++ APIs are provided and designed to work in any recent Linux environment - x86-64 or arm64. They can be extended to other operating systems or environments (e.g. 32-bit) by suitably changing/re-implementing src/hololink/emulation/net.cpp where most of the host-specific functionality has been isolated, though POSIX APIs have been generally been assumed elsewhere. Other than OS facilities, requirements have been kept to a minimum and are described in more detail in the Building HSB Emulator section. HSB Emulator has been tested with host applications in the following environments:

  1. x86 Linux Desktop - Ubuntu 22.04+ - Emulator applications only

  2. Jetson Orin Nano Developer Kit - Jetpack 6.1+ - Emulator applications only

  3. AGX Orin Developer Kit - Jetpack 6.1+ - Host or Emulator applications

  4. IGX Orin Developer Kit - IGX BaseOS 1.0+ - Host or Emulator applications

  5. AGX Thor Developer Kit - Jetpack 7.1+ - Host or Emulator applications

The Examples provided are tested mostly with imx274- (where applicable) and the vb1940-based HSB camera applications. The serve_linux_file and serve_coe_file have been used with the HSB-provided imx477 and imx715 sensor drivers as well.

There are 2 ways to build the HSB Emulator

  • Full

    • This is a normal build of the HSB repository that will build the emulator and include it within the normal hololink Python module

  • Emulator-only

    • Minimal build dependencies. An optional hololink Python module is provided within a virtual environment for the Python API

Build instructions

For all build types, clone and enter the repository

Copy
Copied!
            

git clone https://github.com/nvidia-holoscan/holoscan-sensor-bridge.git cd holoscan-sensor-bridge

Follow the appropriate Host Setup instructions first.

Copy
Copied!
            

mkdir build cmake -S . -B build cmake --build build -j

Install build dependencies. It’s assumed an appropriate CUDA toolkit has been installed for the target system. Follow platform instructions to ensure version, gpu, and driver compatibility:

Copy
Copied!
            

sudo apt-get update sudo apt-get install -y cmake zlib1g-dev

If building the Python bindings as well

Copy
Copied!
            

sudo apt-get install -y python3-pybind11 python3-venv python3-pip

Additional dependencies for the Python bindings are required for the provided examples (numpy v1.23+ and cupy), but the virtual environment that is created will automatically populate them. The versions required are not necessarily compatible with the system Python, especially on Ubuntu 22.04 and comparable or earlier

Copy
Copied!
            

mkdir build cmake -S src/hololink/emulation -B build cmake --build build -j

Configuring the Emulator-only build

Several cmake flags are available to configure the “Emulator-only” build if prerequisites are not met or not in expected locations

dependency

cmake configuration

default setting

Python unavailable/not needed -DHSB_EMULATOR_BUILD_PYTHON=OFF ON
Python virtual environment location
(ignored if Python build is OFF)
-DHSB_EMULATOR_PYTHON_VENV=path/to/virtual_environment ${CMAKE_BINARY_DIR}/env

Note for cmake version < 3.24 (apt default available in Ubuntu 22.04 or earlier), cmake does not allow setting a native architecture and a default CMAKE_CUDA_ARCHITECTURES of 80 86 87 compute capability is given to ensure target Orin platform compatibility. For cmake >= 3.24, native is selected by default.

To facilitate testing and development of HSB Emulator applications, several examples applications in both C++ and Python are provided to demonstrate different possible configurations and implementations. The source code for all the examples are located in src/hololink/emulation/examples. The C++ applications by default will be built to <build directory>/examples/.

For all examples, --help will provide invocation and flag details. All current transport capabilities and therefore the examples will require running as root or under sudo.

When building as “Emulator-only” with Python bindings, the Python examples should be invoked with the Python interpreter in the provided virtual environment. For example, to run the serve_coe_stereo_vb1940_frames example in Python, the full invocation would be

Copy
Copied!
            

sudo build/env/bin/python3 src/hololink/emulation/examples/serve_coe_stereo_vb1940_frames.py 192.168.0.2

example

sample invocation

brief description

HSB sample camera
driver compatibility

Compatible HSB receivers

Notes

serve_linux_file ./serve_linux_file 192.168.0.2 imx_single_raw_frame.dat serve frames from a file imx cameras LinuxReceiver, RoceReceiver

serve_coe_file ./serve_coe_file 192.168.0.2 imx_single_raw_frame.dat serve frames from a file imx cameras LinuxCoeReceiver

serve_coe_vb1940_file ./serve_coe_vb1940_file 192.168.0.2 imx_single_raw_frame.dat serve frames from a file VB1940 LinuxCoeReceiver, SIPLCaptureOp Compatible with AGX Thor platform
serve_linux_single_vb1940_frames ./serve_linux_single_vb1940_frames 192.168.0.2 serve test RGB frames from single VB1940 camera VB1940 LinuxReceiver, RoceReceiver

serve_linux_stereo_vb1940_frames ./serve_linux_stereo_vb1940_frames 192.168.0.2 serve test RGB frames from stereo VB1940 camera VB1940 LinuxReceiver, RoceReceiver

serve_coe_single_vb1940_frames ./serve_coe_single_vb1940_frames 192.168.0.2 serve test RGB frames from single VB1940 camera VB1940 LinuxCoeReceiver, SIPLCaptureOp Compatible with AGX Thor platform
serve_coe_stereo_vb1940_frames ./serve_coe_stereo_vb1940_frames 192.168.0.2 serve test RGB frames from stereo VB1940 camera VB1940 LinuxCoeReceiver, SIPLCaptureOp Compatible with AGX Thor platform

For the *_file examples, it is crucial that image frame sizes are set up appropriately, especially on Jetson AGX Thor platform. You will need to know the correct CSI image frame size that needs to be sent. These are based on Host application driver/operator requirements which are in turn dependent on the camera and HSB hardware. As examples,

  • For imx274 capture in mode 1 (1920X1080 RAW10 - 4 pixels/5 bytes, 175 header bytes, 8 lines of optical black, 8-byte line alignment and 8-byte overall alignment) the CSI frame size is (175 + (((1920 + 3) / 4 * 5 + 7) / 8 * 8) * (1080 + 8) + 7) / 8 * 8 = 2611376 bytes, so the input file should be at least 2611376 bytes in size and each slice of that size will be transmitted as one frame from 192.168.0.2 to an *imx274_player application with the following call:

    • serve_linux_file -s 2611376 192.168.0.2 my_file.dat

  • For vb1940 capture in mode 0 (2560X1984 RAW10 - 4 pixels/5 bytes, 1 line of prefix status, 2 lines of postfix status, 8-byte line alignment and 8-byte overall alignment) the CSI frame size is ((((2560 + 3) / 4 * 5 + 7) / 8 * 8) * (1984 + 3) + 7) / 8 * 8 = 6458400 bytes, but on Jetson AGX Thor, it additionally goes through a swizzle/packetizer step to a raw10 format (3 pixels/4 bytes, 64-byte line alignment) such that the CSI frame size is effectively ((((2560 + 2) / 3 * 4 + 63) / 64 * 64) * (1984 + 3) + 7) / 8 * 8 = 6867072 bytes. Therefore on Jetson AGX Thor, a similar call would be:

    • serve_coe_vb1940_file -s 6867072 192.168.0.2 my_file.dat

For further details on other modes see the provided drivers in python/hololink/sensors/.

By default the *file examples will assume the whole file is one image frame. To set the image frame size, the -s flag is available in each of the examples. When the file size is greater than the value supplied to the -s option, it will assume multiple frames are in the file and cycle through them, advancing by -s bytes after each image frame and reset to the start of the file when a full frame can no longer be sent. For example, if X is provided to the -s option, -s X, and the file size is N * X + Y bytes where N > 0, and 0 <= Y < X, the examples will send N image frames, reset to the beginning of the file, and then continue.

Full-loop testing of the HSB Emulator is provided within the pytest framework in the docker container. To run the test, launch the docker container and run:

Copy
Copied!
            

pytest

the same as described in Run tests in the demo container. The default pytest configuration will test vb1940 camera modes with linux sockets in roce and coe packet formats for gpu or cpu inputs as well as linux roce with imx477 camera modes. To run an extended sweep of parameters including also imx715 and imx274 camera mode, run:

Copy
Copied!
            

pytest --emulator

to get the more comprehensive testing parameters.

Hardware Loopback

The tests above for vb1940 are all executed by default over software loopback. To run them over-the-wire using available network interfaces, they may be run in hardware loopback mode by attaching an Ethernet cable between 2 Ethernet-compatible ports on the target test device. Note the interfaces IF1 and IF2 (from e.g. ip addr sh) that correspond to the attached ports. Then run the tests with the following option:

Copy
Copied!
            

pytest --hw-loopback IF1,IF2

This will run the same tests for the HSB Emulator, but isolate IF1 from IF2 and launch the relevant the emulator process on IF1 and the test host application process targeting IF2.

The scripts nsisolate.sh, nsexec.sh, nspid.sh, and nsjoin.sh in the scripts/ directory are provided to facilitate development of additional hardware loopback tests. The --hw-loopback IF1,IF2 switch in pytest is roughly equivalent to:

Copy
Copied!
            

# setup environment scripts/nsisolate.sh IF1 192.168.0.2/24 # for each test scripts/nsexec.sh IF1 <emulator example command + args> # teardown environment scripts/nsjoin.sh IF1 <old IP address to reset>

The nspid.sh script facilitates finding the relevant pid with which to control a process within the network namespace created by nsisolate.sh since you cannot simply control/kill the nsexec.sh command without creating an orphaned process.

Accelerated Networking on Hardware Loopback

On platforms that offer accelerated networking such as Jetson AGX Thor (SIPL COE over MGBE), IGX Orin (RoCEv2 over CX-7), and DGX Spark (RoCEv2 over CX-7), the network namespace scripts above also provide a simple way to test accelerated networking HSB applications.

Under normal conditions, the Linux operating systems used on those target devices will route connections between two interfaces through an internal software/loopback interface. Isolating one of the connected interfaces in a network namespace will prevent the software loopback rerouting and force use of the accelerating hardware. The HSB Emulator tests for RoCE will already handle this and on relevant platforms will automatically configure a network namespace for testing when provided with the --hw-loopback parameters. On AGX Thor with SIPL, the MGBE interface needs to be managed independently but the tests will still run over the isolated interface if provided. As examples:

Copy
Copied!
            

# interface names may vary pytest --hw-loopback enP5p3s0f1np1,enP5p3s0f0np0

will run the linux, coe transport examples over standard linux sockets and also run the RoCE examples for vb1940 through the CX-7 ports.

Copy
Copied!
            

scripts/nsisolate.sh enP2p1s0 192.168.0.2/24 # this next line run outside the container if mgbe0_0 is "DOWN" after isolating sudo ip link set mgbe0_0 down && sudo ip link set mgbe0_0 up # back in the container pytest --hw-loopback enP2p1s0,mgbe0_0 --json-config my_single_vb1940_sensor_config.json scripts/nsjoin.sh enP2p1s0

will run the linux, coe transport examples over standard linux sockets and also run the SIPL example for vb1940 through the MGBE port with COE offloading.

Primary Classes

For all the classes below, the C++ namespace is assumed hololink::emulation and the Python module is loaded as import hololink.emulation as hemu. Python-equivalent examples or definitions are given for clarity for non-trivial signatures. For further details including private and protected members of classes for development purposes, see the source code in src/hololink/emulation.

HSBEmulator

The HSB Emulator class is used for command and control communication between the Emulator device and the Host HSB Application

HSBEmulator <–> Hololink in host HSB application

class hololink::emulation::HSBEmulator

The HSBEmulator class represents the interface that a host application has to an HSB and acts as the emulation device’s controller.

It manages the DataPlane objects and the I2CController and all communication with the internal memory model; see MemRegister for more details.

Public Functions

HSBEmulator(const HSBConfiguration &config)

python:

hemu.HSBEmulator(config: hemu.HSBConfiguration = hemu.HSB_EMULATOR_CONFIG)

Construct a new HSBEmulator object with the specified configuration.

Note

HSB_EMULATOR_CONFIG is roughly equivalent to a Lattice board.

Note

HSB_LEOPARD_EAGLE_CONFIG is roughly equivalent to a Leopard Eagle board.

Parameters

config – The configuration of the emulator. Two fully populated options are provided in hsb_config.hpp: HSB_EMULATOR_CONFIG or HSB_LEOPARD_EAGLE_CONFIG.

HSBEmulator()

Construct a new HSBEmulator object.

Defaults to HSB_EMULATOR_CONFIG, which is roughly equivalent to a Lattice board.

void start()

Start the emulator.

This will start the BootP broadcasting via the DataPlane objects as well as the control thread to listen for control messages from the host.

Note

It is safe to call this function multiple times and after a subsequent call to stop()

void stop()

Stop the emulator.

This will shut down the control message thread, all BootP broadcasts in DataPlane objects. Data transmission is still possible until HSBEmulator object is destroyed

Note

It is safe to call this function multiple times

bool is_running()

Check if the emulator is running.

Returns

true if the emulator is running, false otherwise.

void write(uint32_t address, uint32_t value)

python:

def write(self: hemu.HSBEmulator, address: int, value: int)

Write a value to a register.

Parameters
  • address – The address of the register to write.

  • value – The value to write.

uint32_t read(uint32_t address)

python:

def read(self: hemu.HSBEmulator, address: int) -> int

Read a value from a register.

Parameters

address – The address of the register to read.

Returns

The value read.

I2CController &get_i2c(uint32_t controller_address = hololink::I2C_CTRL)

python:

def get_i2c(self: hemu.HSBEmulator, controller_address: int = hololink.I2C_CTRL) -> hemu.I2CController

Get a reference to the I2C controller.

Returns

A reference to the I2C controller.

class hololink::emulation::MemRegister

Representation of the internal memory space of the HSBEmulator and its registers.

Note

This currently implemented as a simple map, but the underlying implementation should be expected to change, though the public methods should remain available.

Public Functions

MemRegister(const HSBConfiguration &configuration)

Construct a new MemRegister object.

Parameters

configuration – The configuration of the HSBEmulator.

void write(uint32_t address, uint32_t value)

Write a value to a register.

Parameters
  • address – The address of the register

  • value – The value to write to the register

void write_many(const std::vector<std::pair<uint32_t, uint32_t>> &address_values)

Convenience functions to write multiple values to registers under a single lock operations.

Parameters

address_values – A vector of pairs of register addresses and values to write. Accepts either a vector or a static initializer list.

void write_many(const std::initializer_list<std::pair<uint32_t, uint32_t>> &address_values)
uint32_t read(uint32_t address)

Read a value from a register.

Parameters

address – The address of the register

Returns

The value of the register

std::vector<uint32_t> read_many(const std::vector<uint32_t> &addresses)

Convenience functions to read multiple values from registers under a single lock operations.

Parameters

addresses – A vector of register addresses to read. Accepts either a vector or a static initializer list.

Returns

A vector of values read from the registers

std::vector<uint32_t> read_many(const std::initializer_list<uint32_t> &addresses)
void write_range(uint32_t start_address, const std::vector<uint32_t> &values)

Write a range of values to registers.

Parameters
  • start_address – The address of the first register to write

  • values – A vector of values to write to the registers

std::vector<uint32_t> read_range(uint32_t start_address, uint32_t num_addresses)

Read a range of values from registers.

Parameters
  • start_address – The address of the first register to read

  • num_addresses – The number of registers to read

Returns

A vector of values read from the registers

I2CController is a component in HSBEmulator that is only needed to develop a sensor emulator/driver bridge for I2C sensors.

class hololink::emulation::I2CController

class that manages I2C transaction events from the host runs a separate thread for execution but only one event may ever be executed at a time for each controller

Note

This should not be instantiated directly by user code. Use HSBEmulator::get_i2c() instead. The default and primary I2CController is at address hololink::I2C_CTRL.

Public Functions

void attach_i2c_peripheral(uint32_t bus_address, uint8_t peripheral_address, I2CPeripheral *peripheral)

python:

def attach_i2c_peripheral(self: hemu.I2CController, bus_address: int, peripheral_address: int, peripheral: hemu.I2CPeripheral)

attach an I2C peripheral as a callback on the specified (bus address, peripheral address) pair.

Parameters
  • bus_address – The bus address of the peripheral. For multiplexing or bus expanded addressing, this is the bus address of the peripheral.

  • peripheral_address – The peripheral address of the peripheral.

  • peripheral – The peripheral to attach. This is a pointer to the peripheral object. The caller is responsible for managing the lifetime of the peripheral object.

DataPlanes

DataPlanes are the object with which client applications of the HSB Emulator send data to the Host application and how receivers identify unique sensor sources

DataPlane –> appropriate subclass of BaseReceiverOp in host HSB Application

class hololink::emulation::DataPlane

DataPlane is the partially implemented Abstract Base Class with which HSB Emulator applications control data transmission and HSB Host applications establish “connections” and data flow via Bootp enumeration.

The parent instance of any DataPlane implementation will manage Bootp broadcasting. Each implementation must interface with the HSB Emulator’s internal memory model (MemRegister) to update and manage metadata and any state needed for the metadata or transport layer for the corresponding Transmitter.

Note

The DataPlane lifecycle operations are managed by the HSBEmulator instance it is attached to and therefore DataPlane lifetime must be at least as long as the HSBEmulator instance or at least until the final HSBEmulator::stop() is called.

Subclassed by hololink::emulation::COEDataPlane, hololink::emulation::DataPlanePublicist, hololink::emulation::LinuxDataPlane, hololink::emulation::PyDataPlane

Public Functions

DataPlane(HSBEmulator &hsb_emulator, const IPAddress &ip_address, uint8_t data_plane_id, uint8_t sensor_id)

Constructs a DataPlane object.

Parameters
  • hsb_emulator – A reference to an HSBEmulator instance that the DataPlane will configure itself from.

  • ip_address – The IP address of the DataPlane.

  • data_plane_id – The data plane index of the DataPlane.

  • sensor_id – The sensor index of the DataPlane to associate with the DataPlane. The data_plane_id and sensor_id are used to identify registers needed to compile metadata.

void start()

Start the DataPlane by initiating the BootP broadcast.

void stop()

Stop the DataPlane by stopping the BootP broadcast.

inline void stop_bootp()

This is a clear alias for stop()

bool is_running()

Check if the DataPlane is running.

(Bootp is broadcasting)

Returns

True if the DataPlane is running, false otherwise.

int64_t send(const DLTensor &tensor)

Send a tensor over the DataPlane.

Note

This method is synchronous. It will block and metadata will be protected by a mutex until the send is complete.

Parameters

tensor – The tensor object reference to send. Supported device types are kDLCPU, kDLCUDA, kDLCUDAHost (host pinned), and kDLCUDAManaged (Unified Memory)

Returns

The number of bytes sent or < 0 if error occurred.

inline uint8_t get_sensor_id() const

Get the sensor ID associated with the DataPlane.

Returns

The sensor ID.

bool packetizer_enabled() const

Check if the packetizer is enabled.

Returns

True if the packetizer is enabled, false otherwise.

The IPAddress and the utility function IPAddress_from_string are provided to encapsulate and simplify construction of the network interface properties required for the DataPlane object in the hosted environment

struct hololink::emulation::IPAddress

python (limited API):

def __init__(self: hemu.IPAddress, ip_address: str, /)

Encapsulates all the relevant information for transmission from an Emulator device IP address

This structure contains the network interface configuration required for the emulator to send packets, including interface name, IP address, MAC address, optional port and broadcast address (defaulting to 255.255.255.255 if on interface that does not have an explicit broadcast, e.g. loopback).

Public Members

std::string if_name

Network interface name (e.g., “eth0”, “lo”)

in_addr_t ip_address = {0}

IP address in network byte order.

in_addr_t subnet_mask = {0}

Subnet mask in network byte order.

in_addr_t broadcast_address = {0}

Broadcast address in network byte order.

hololink::core::MacAddress mac = {0}

MAC address of the interface.

uint16_t port = {hololink::DATA_SOURCE_UDP_PORT}

UDP port number (defaults to DATA_SOURCE_UDP_PORT)

uint8_t flags = {0}

Configuration flags indicating which fields are valid.

IPAddress hololink::emulation::IPAddress_from_string(const std::string &ip_address)

python (use IPAddress object constructor)

Construct an IPAddress object from a string representation of the IP address.

Parameters

ip_address – The string representation of the IP address. Currently must be in format accepted by inet_addr().

Returns

An IPAddress instance

Two implementations of the DataPlane interface provided are LinuxDataPlane and COEDataPlane

RoCEv2 UDP-based transport implementation.

class hololink::emulation::LinuxDataPlane : public hololink::emulation::DataPlane

The DataPlane implementation for RoCEv2 UDP transport.

Public Functions

LinuxDataPlane(HSBEmulator &hsb_emulator, const IPAddress &source_ip, uint8_t data_plane_id, uint8_t sensor_id)

python:

def __init__(self: hemu.LinuxDataPlane, hsb_emulator: hemu.HSBEmulator, source_ip: hemu.IPAddress, data_plane_id: int, sensor_id: int)

Construct a new LinuxDataPlane object

Parameters
  • hsb_emulator – The HSBEmulator object to attach to.

  • source_ip – The IP address of the DataPlane.

  • data_plane_id – The identifying index of the DataPlane.

  • sensor_id – The identifying index of the sensor interface associated with the DataPlane.

class hololink::emulation::COEDataPlane : public hololink::emulation::DataPlane

The DataPlane implementation for IEEE 1722B Camera-over-Ethernet link layer transport.

Public Functions

COEDataPlane(HSBEmulator &hsb_emulator, const IPAddress &source_ip, uint8_t data_plane_id, uint8_t sensor_id)

python:

def __init__(self: hemu.COEDataPlane, hsb_emulator: hemu.HSBEmulator, source_ip: hemu.IPAddress, data_plane_id: int, sensor_id: int)

Construct a new COEDataPlane object

Parameters
  • hsb_emulator – The HSBEmulator object to attach to.

  • source_ip – The IP address of the DataPlane.

  • data_plane_id – The identifying index of the DataPlane.

  • sensor_id – The identifying index of the sensor interface associated with the DataPlane.

Transmitters - For transport development

Transmitters represent the actual transport implementation that is used by the DataPlane to send the data.

For development of new/modified transport only. HSBEmulator applications currently do not interact directly with these objects

class hololink::emulation::BaseTransmitter

Abstract base class for all transmitters.

This class is used to send DLPack tensors to the destination to interfacing with a variety of array memory models.

Subclassed by hololink::emulation::COETransmitter, hololink::emulation::LinuxTransmitter

Public Functions

virtual int64_t send(const TransmissionMetadata *metadata, const DLTensor &tensor) = 0

Send a tensor to the destination.

Note

The tensor is not owned by the transmitter and must not be propagated to other objects to satisfy the DLPack Python API specification.

Parameters
  • metadata – The metadata for the transmission. This is always aliased from the appropriate type of metadata for the Transmitter instance.

  • tensor – The tensor to send. See dlpack.h for its contents and semantics.

Returns

The number of bytes sent or < 0 on error

Two implementations of the BaseTransmitter interface provided are LinuxTransmitter and COETransmitter

class hololink::emulation::LinuxTransmitter : public hololink::emulation::BaseTransmitter

The LinuxTransmitter implements the BaseTransmitter interface and encapsulates the transport over RoCEv2.

Public Functions

LinuxTransmitter(const IPAddress &source_ip)

Construct a new LinuxTransmitter object.

Note

The MAC address is derived from the interface name using the mac_from_if function in net.hpp.

Parameters

source_ip – The IP address to be used as the source address of the transmitter.

LinuxTransmitter(const LinuxHeaders &headers)

Construct a new LinuxTransmitter object.

Parameters

headers – The fully configurable headers to use. See source code for details

virtual int64_t send(const TransmissionMetadata *metadata, const DLTensor &tensor) override

Send a tensor to the destination using the TransmissionMetadata provided.

Implementation of BaseTransmitter::send interface method.

Parameters
  • metadata – The metadata for the transmission. This is always aliased from the appropriate type of metadata for the Transmitter instance.

  • tensor – The tensor to send. See dlpack.h for its contents and semantics.

Returns

The number of bytes sent or < 0 on error

class hololink::emulation::COETransmitter : public hololink::emulation::BaseTransmitter

The COETransmitter implements the BaseTransmitter interface and encapsulates the transport over IEEE 1722B.

Public Functions

COETransmitter() = delete
COETransmitter(const std::string &if_name)

Construct a new COETransmitter object.

Note

The MAC address is derived from the interface name using the mac_from_if function in net.hpp.

Parameters

if_name – The name of the network interface to use.

COETransmitter(const std::string &if_name, const std::array<uint8_t, 6> &mac_src)

Construct a new COETransmitter object.

Parameters
  • if_name – The name of the network interface to use.

  • mac_src – The MAC address to use.

COETransmitter(const std::string &if_name, const COEHeaders &headers)

Construct a new COETransmitter object.

Parameters
  • if_name – The name of the network interface to use.

  • headers – The fully configurable headers to use. See source code for details

virtual int64_t send(const TransmissionMetadata *metadata, const DLTensor &tensor) override

Send a tensor to the destination using the TransmissionMetadata provided.

Implementation of BaseTransmitter::send interface method.

Parameters
  • metadata – The metadata for the transmission. This is always aliased from the appropriate type of metadata for the Transmitter instance.

  • tensor – The tensor to send. See dlpack.h for its contents and semantics.

Returns

The number of bytes sent or < 0 on error

I2CPeripheral - For virtual sensor or driver bridge development

For development of new sensor bridge drivers (bridge HSBEmulator to a real device) or emulating a sensor.

class hololink::emulation::I2CPeripheral

Abstract base class for all I2C-based emulated sensors giving them access to I2C transactions originating from the HSB host.

Note

configuration of the “bus” is done by HSBEmulator calling get_i2c_config(). Derived class can assume this only happens at most once before any calls to start() or reset() without an intervening call to stop()

Subclassed by hololink::emulation::PyI2CPeripheral, hololink::emulation::RenesasI2CPeripheral, hololink::emulation::sensors::Vb1940Emulator

Public Functions

inline virtual void start()

start the peripheral.

This will be called when I2CController itself starts and should not be done by the client code

virtual void attach_to_i2c(I2CController &i2c_controller, uint8_t bus_address) = 0

python:

def attach_to_i2c(self: hemu.I2CPeripheral, i2c_controller: hemu.I2CController, bus_address: int)

attach the peripheral to the I2C controller. This should be called by client code before start() is called

Parameters
  • i2c_controller – The I2C controller to attach to. Retrieved from HSBEmulator with get_i2c(controller_address)

  • bus_address – The bus address of the peripheral.

inline virtual void stop()

stop the peripheral.

This will be called when the I2CController is stopped and should not be done by the client code

Note

Peripheral should release all resources in case a subsequent start() is called.

virtual I2CStatus i2c_transaction(uint8_t peripheral_address, const std::vector<uint8_t> &write_bytes, std::vector<uint8_t> &read_bytes) = 0

python:

def i2c_transaction(self: hemu.I2CPeripheral, peripheral_address: int, write_bytes: List[int], read_bytes: List[int]) -> hemu.I2CStatus

perform an I2C transaction. This will be called by the I2CController when a transaction is requested.

Note

read request size is the size of the read_bytes vector as initialized by the caller

Parameters
  • peripheral_address – The peripheral address to communicate with.

  • write_bytes – The bytes to write to the peripheral.

  • read_bytes – The bytes to read from the peripheral. This shall be filled with 0s to the requested read size and replaced by peripheral. This is to ensure peripheral can get the read count from read_bytes.size() and enough valid data is returned for no-ops.

Returns

I2CStatus The status of the transaction.

The primary example of implementing an I2CPeripheral for an HSB Emulator application is the STM VB1940 sensor for emulating the Leopard Eagle HSB from Leopard Imaging.

class hololink::emulation::sensors::Vb1940Emulator : public hololink::emulation::I2CPeripheral

Public Functions

virtual void attach_to_i2c(I2CController &i2c_controller, uint8_t bus_address) override

python:

def attach_to_i2c(self: hemu.sensors.Vb1940Emulator, i2c_controller: hemu.I2CController, bus_address: int)

Attach the Vb1940Emulator to an I2C controller at a specified bus

Parameters
  • i2c_controller – The I2C controller to attach to. Retrieved from HSBEmulator with get_i2c(controller_address)

  • bus_address – The bus address of the I2C controller

virtual I2CStatus i2c_transaction(uint8_t peripheral_address, const std::vector<uint8_t> &write_bytes, std::vector<uint8_t> &read_bytes) override

python:

def i2c_transaction(self: hemu.sensors.Vb1940Emulator, peripheral_address: int, write_bytes: List[int], read_bytes: List[int]) -> hemu.I2CStatus

Receive and act on I2C transaction from HSBEmulator’s I2C controller(s)

Parameters
  • peripheral_address – The address of the peripheral to transaction with

  • write_bytes – The bytes to write to the peripheral

  • read_bytes – The bytes to read from the peripheral

Returns

The status of the I2C transaction

bool is_streaming() const

Check if the Host application has set the sensor to streaming mode.

It is expected that the host application does not send any commands that set the sensor to streaming mode if the sensor is not yet configured.

Returns

True if the Vb1940Emulator is streaming, false otherwise

uint16_t get_pixel_width() const

Get the width of the configured image in pixels.

Returns

The width of the image in pixels or 0 if the sensor has not been configured; see is_streaming().

uint16_t get_pixel_height() const

Get the height of the configured image in pixels.

Returns

The height of the image in pixels or 0 if the sensor has not been configured; see is_streaming().

uint16_t get_bytes_per_line() const

Get the number of bytes per line of the sensor CSI image frame - not the packetized CSI image frame.

Returns

The number of bytes per line of the CSI image frame or 0 if the sensor has not been configured; see is_streaming().

uint16_t get_image_start_byte() const

Get the start byte offset for the CSI image frame.

Returns

The byte offset of the CSI image frame where the image starts or 0 if the sensor has not been configured; see is_streaming().

uint32_t get_csi_length() const

Get the length of the CSI data in bytes for the CSI image frame - not the packetized CSI image frame.

Returns

The length of the CSI data in bytes or 0 if the sensor has not been configured; see is_streaming().

uint8_t get_pixel_bits() const

Get the number of bits per pixel.

Returns

The number of bits per pixel or 0 if the sensor has not been configured; see is_streaming().

Structure of an HSB Emulator application

The Examples in the next section and the main functions in their source code show typical workflows for starting up an HSBEmulator instance, but the general outline in C++ and Python is given below for the case of RoCEv2 transmission over Linux sockets.

Copy
Copied!
            

// including the appropriate header for the implementation of the // DataPlane is sufficient for a minimal application #include "hololink/emulation/linux_data_plane.hpp" // OPTIONAL: import the target sensor emulator #include "hololink/emulation/sensors/vb1940_emulator.hpp" // Declare a target HSBEmulator instance. In this case, the example is explicitly // emulating a Leopard Eagle HSB configuration (needed for VB1940). It is not yet // ready to receive commands from a host application until a subsequent call to its // `start()` method. // NOTE: If multiple instances of HSBEmulator exist on a single device // (even across // processes), they can receive and emit independent ECB packets, which // may conflict with the host application. HSBEmulator hsb(HSB_LEOPARD_EAGLE_CONFIG); // Create an implementation of the DataPlane interface which will be used to send data // as if from a *single* sensor. For this, you need to set a sensor_id to identify the // sensor interface the channel will emulate and IPAddress + data_plane_id that identify // NOTE: multiple DataPlanes with IPAddress or data_plane_id will result in multiple // BOOTP broadcast message sources // NOTE: if multiple DataPlanes have the same (data_plane_id, sensor_id) combination, // their configurations may override each other uint8_t data_plane_id = 0; uint8_t sensor_id = 0; LinuxDataPlane linux_data_plane(hsb, IPAddress_from_string(ip_address), data_plane_id, sensor_id); // Optional for stateful sensor emulation or driver bridge declare the sensor instance sensors::Vb1940Emulator vb1940; // Attach it to an appropriate I2CController and bus. On Leopard Eagle HSB, the i2c bus // address is the sensor_id offset from CAM_I2C_BUS vb1940.attach_to_i2c(hsb.get_i2c(hololink::I2C_CTRL), hololink::CAM_I2C_BUS + sensor_id); // Start the emulator. After this, the HSBEmulator instance can receive and respond // to control commands from the host application. All DataPlane instances which have // been declared will start emitting BOOTP broadcast messages. All I2CPeripherals // will receive a call to their start() methods hsb.start(); // client code in a loop. Passing the DataPlane implementation gives the loop access // to send data to the host application and sending the sensor instance (if needed) // provides access to sensor-specific/state data application_specific_data_loop(LinuxDataPlane, vb1940, ...user configuration data) // OPTIONAL: Stop the emulator if the application needed to be able to restart the // HSBEmulator instance without exiting. All registered elements will receive a call // to their `stop()` methods for cleanup. The emulator and all registered elements // can be re-started with a subsequent call to // `start()`. hsb.stop();

Copy
Copied!
            

# importing the appropriate module for the implementation of the DataPlane is sufficient for # a minimal application import hololink.emulation as hemu # import the hololink module for access to some HSB constants. # NOTE: this is very minimal in the "Emulator-only" build and does not include any of the # other hololink libraries import hololink as hololink_module # Declare a target HSBEmulator instance. In this case, the example is explicitly emulating a # Leopard Eagle HSB configuration (needed for VB1940). It is not yet ready to receive # commands from a host application until a subsequent call to # its `start()` method. # NOTE: If multiple instances of HSBEmulator exist on a single device (even across # processes), they can receive and emit independent ECB packets, which may conflict with the # host application. hsb = hemu.HSBEmulator(hemu.HSB_LEOPARD_EAGLE_CONFIG) # Create an implementation of the DataPlane interface which will be used to send data as if # from a *single* sensor. For this, you need to set a sensor_id to identify the sensor # interface the channel will emulate and IPAddress + data_plane_id that identify # NOTE: multiple DataPlanes with IPAddress or data_plane_id will result in multiple BOOTP # broadcast message sources # NOTE: if multiple DataPlanes have the same (data_plane_id, sensor_id) combination, their # configurations may override each other data_plane_id = 0 sensor_id = 0 data_plane = hemu.LinuxDataPlane( hsb, hemu.IPAddress(args.ip_address), data_plane_id, sensor_id) # Optional for stateful sensor emulation or driver bridge declare the sensor instance vb1940 = hemu.sensors.Vb1940Emulator() # Attach it to an appropriate I2CController and bus. On Leopard Eagle HSB, the i2c bus # address is the sensor_id offset from CAM_I2C_BUS vb1940.attach_to_i2c( hsb.get_i2c(hololink_module.I2C_CTRL), hololink_module.CAM_I2C_BUS + sensor_id ) # Start the emulator. # After this, the HSBEmulator instance can receive and respond to control commands from the # host application. All DataPlane instances which have been declared will start emitting # BOOTP broadcast messages. All I2CPeripherals will receive a call to their start() methods hsb.start() # sensor client code in a loop. Passing the DataPlane implementation gives the loop access to # send data to the host application # and sending the sensor instance (if needed) provides access to sensor-specific/state data application_specific_data_loop(LinuxDataPlane, vb1940, ...user configuration data) # OPTIONAL: Stop the emulator if the application needed to be able to restart the HSBEmulator # instance without exiting. All registered elements will receive a call to their `stop()` # methods for cleanup. The emulator and all registered elements can be re-started with a # subsequent call to `start()`. hsb.stop()

For stereo/dual sensor setups, see the *stereo_vb1940_frames examples. The specific loops used to stream the files or test frames across all the examples can be found in the emulator_utils files.

Problem Description

Root Cause

Corrective Action (s)

“Permission Denied”/”Operation not permitted” in socket creation or bind UDP/port combination is protected by capabilities privilege - Run under sudo
- Check firewall status and if necessary ensure the ports are open for UDP.
Example (Ubuntu 24.04 UDP to/from IGX on port 192.168.0.101):
sudo ufw status # check for UDP access, specifically on port 8192
sudo ufw allow from 192.168.0.101 to any port 8192
- use sudo setcap cap_net_raw+ep /path/to/executable to remove raw socket restrictions. WARNING: allowing this on the Python interpreter has severe security implications
white streaks/lost packets in frame visible on receiver end on the receiver host device, make sure your have sufficient network receiver buffer size. Common with LinuxReceiverOp and LinuxCoeReceiverOp, esp on AGX devices. sudo sysctl -w net.core.rmem_max=16777216 && sudo sysctl -w net.core.rmem_default=16777216
running with Python code:
AttributeError: ‘numpy.ndarray’ object has no attribute ‘__dlpack__’
or similar with data types other than ‘numpy.ndarray’
data type (in this case numpy array) does not support Python array API DLPack data interchange format - change to a data type that supports DLPack
- upgrade environment to version of package that does support DLPack (numpy requires 1.23+)
frequent I2C failures with emulator sending to Thor host device failed packet receipt on Thor mGbE interface with no retries in JP 7.0 with SIPL rel 38.2.1 upgrade to JP 7.1

rm -rf build

Previous Adapting new sensors
Next Holoscan Sensor Bridge FPGA IP
© Copyright 2022-2024, NVIDIA. Last updated on Nov 6, 2025