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
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
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
LinuxTransmitterfor RoCEv2 UDP-based transportName is to be consistent with the rest of HSB where the implementation is via Linux interface access and sockets
COETransmitterfor IEEE 1722B link layer transport (Camera-over-Ethernet, CoE)BaseTransmitterandDataPlaneinterfaces for adding experimental transport protocolsUnderlying C++ implementation + Python API
CUDA buffer support
DLPackarray interface format - compatible with contiguous arrays from any Python package that supportsDLPacksensor data buffers may be provided on host or CUDA GPU device
Configuration of “stateful” sensors via an
I2CPeripheralinterfaceSPI and GPIO are not fully implemented
Isolated build from host
HSB Emulator may be built without linking to either HSB or Holoscan SDK to minimize environmental and package dependencies
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.
RoceReceiverOpandSIPLCaptureOp, 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.
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
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:
x86 Linux Desktop - Ubuntu 22.04+ - Emulator applications only
Jetson Orin Nano Developer Kit - Jetpack 6.1+ - Emulator applications only
AGX Orin Developer Kit - Jetpack 6.1+ - Host or Emulator applications
IGX Orin Developer Kit - IGX BaseOS 1.0+ - Host or Emulator applications
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
git clone https://github.com/nvidia-holoscan/holoscan-sensor-bridge.git
cd holoscan-sensor-bridge
Follow the appropriate Host Setup instructions first.
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:
sudo apt-get update
sudo apt-get install -y cmake zlib1g-dev
If building the Python bindings as well
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
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
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 | 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_playerapplication 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:
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:
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:
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:
# 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:
# 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.
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
HSBEmulatorclass represents the interface that a host application has to an HSB and acts as the emulation device’s controller.It manages the
DataPlaneobjects and theI2CControllerand all communication with the internal memory model; seeMemRegisterfor 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.
NoteHSB_EMULATOR_CONFIG is roughly equivalent to a Lattice board.
NoteHSB_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.
NoteIt 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
NoteIt 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) -> intRead 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.I2CControllerGet a reference to the I2C controller.
- Returns
A reference to the I2C controller.
-
HSBEmulator(const HSBConfiguration &config)
-
class hololink::emulation::MemRegister
Representation of the internal memory space of the HSBEmulator and its registers.
NoteThis 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
-
MemRegister(const HSBConfiguration &configuration)
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
NoteThis 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.
-
void attach_i2c_peripheral(uint32_t bus_address, uint8_t peripheral_address, I2CPeripheral *peripheral)
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
DataPlaneimplementation 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 correspondingTransmitter.NoteThe DataPlane lifecycle operations are managed by the
HSBEmulatorinstance it is attached to and thereforeDataPlanelifetime must be at least as long as theHSBEmulatorinstance or at least until the finalHSBEmulator::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.
NoteThis 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.
-
DataPlane(HSBEmulator &hsb_emulator, const IPAddress &ip_address, uint8_t data_plane_id, uint8_t sensor_id)
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.
-
std::string if_name
-
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.
-
LinuxDataPlane(HSBEmulator &hsb_emulator, const IPAddress &source_ip, uint8_t data_plane_id, uint8_t sensor_id)
-
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.
-
COEDataPlane(HSBEmulator &hsb_emulator, const IPAddress &source_ip, uint8_t data_plane_id, uint8_t sensor_id)
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.
NoteThe 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
-
virtual int64_t send(const TransmissionMetadata *metadata, const DLTensor &tensor) = 0
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.
NoteThe 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
-
LinuxTransmitter(const IPAddress &source_ip)
-
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.
NoteThe 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
-
COETransmitter() = delete
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.
Noteconfiguration 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
NotePeripheral 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.I2CStatusperform an I2C transaction. This will be called by the I2CController when a transaction is requested.
Noteread 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.
-
inline virtual void start()
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.I2CStatusReceive 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().
-
virtual void attach_to_i2c(I2CController &i2c_controller, uint8_t bus_address) override
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.
// 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();
# 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 8192sudo 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