# Xavia SDK examples in C++

This library contains the following examples to help users get going quickly.

Each example focuses on a specific topic or feature.
You should combine the techniques demonstrated here with your own code to build a fully functional integration.

- [Xavia SDK examples in C++](#xavia-sdk-examples-in-c)
  - [Building the examples](#building-the-examples)
  - [Basic example](#basic-example)
  - [Point cloud handling](#point-cloud-handling)
  - [Point cloud file output](#point-cloud-file-output)
  - [Error handling](#error-handling)
  - [Event handling](#event-handling)
  - [Logging](#logging)
  - [Query information](#query-information)
  - [System power off](#system-power-off)
  - [Camera Examples](#camera-examples)
    - [Camera basic](#camera-basic)
    - [Recording camera images as jpg](#recording-camera-images-as-jpg)
    - [Combining Lidar Camera example](#combining-lidar-camera-example)
    - [Combined recorder](#combined-recorder)

---

## Building the examples

There are two ways to build the examples:

### As a standalone project

Build the examples directory as a top-level CMake project (adjust to match your generator and environment):

```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
```

This creates executable targets for each example that you can run from the commandline.

### As part of your project

When the Xavia SDK is included in your own `CMakeLists.txt`, the examples are available as CMake targets (e.g., `xavia::example_basic`).
You can build them alongside your code.
See the full user manual on how to include the Xavia SDK into your own projects.

## Basic example

- **See**: [examples/basic/Example_basic.cpp](basic/Example_basic.cpp)
- **CMake target name**: `xavia::example_basic`

The basic examples provides the minimal working version of an application that runs the sensor for a fixed period of time.
In this example all the data is ignored, the default configuration is used an and no error handling is performed.
As such, it is not suitable for production use, but it will show you how easy it is to get started with the Xavia SDK.

The following steps are taken:

1. Create a sensor object:

   ```cpp
   xm::SensorFactory factory;
   auto sensor = factory.Build(ip, port);
   ```

2. Run the sensor:

   ```cpp
   sensor->Start();
   //...
   sensor->Stop();
   ```

> Note: the `ip` and `port` are optional. The default values are suited for the factory configuration of the lidar.

---

## Point cloud handling

- **See**: [examples/pointCloudHandling/Example_pointCloudHandling.cpp](pointCloudHandling/Example_pointCloudHandling.cpp)
- **CMake target name**: `xavia::example_xpc`

In this example, the basic example is extended with point cloud handling.
This means that the incoming point clouds are captured using a callback and 'processed'.
In this example, the processing is done in a separate `PointCloudPrinter` object.
For each incoming point cloud object, it requests the frame and line counters from the metadata:

```cpp
pc.GetMetaData().frameNumber;
pc.GetMetaData().lineNumber;
```

The line numbers are logged into a vector and the information is printed to the console when a new frame arrives.

> Note: In default conditions point cloud lines arrive at more than 1kHz. Printing or logging output for every line is not advised as this would slow down the application to the point where it will not be able to capture every point cloud. Logging to a file will quickly fill the memory.

Accessing the point data can be done using the other getter functions:

```cpp
std::vector<float> distanced_mm = pc.GetDistanceMm();
std::vector<float> x_mm = pc.GetXMm();
```

This delivery is optimized for further vectorized calculations.

---

## Point cloud file output

- **See**: [examples/ser-deser/Example_serdeser.cpp](ser-deser/Example_serdeser.cpp)
- **CMake target name**: `xavia::example_serdeser`

This example demonstrates how to user serialization and deserialization to store the point cloud in a binary format.
This format is a proprietary format of XenomatiX that contains all the information transferred on the ethernet connection.
The SDK provides both a `Serialize()` and `Deserialize()` function in order to convert the point cloud object to and from binary form.
The binary form is represented by a `std::vector<std::uint8_t>`.
The std::vector guarantees data is stored in a single continuous memory chunk, as such, the data can be easily and efficiently copied using e.g. `std::memcpy(dst, buf.data(), buf.size());` (here also using the fact that `sizeof(std::uint8_t) == 1`).

The example code exists out of two parts:

- PART 1: demonstrates the use of serialization during live streaming.
- PART 2: demonstrates the use of deserialization in offline context.

In part 1, each point cloud is captured using a callback as demonstrated in [the point cloud handling example](#point-cloud-handling).
Within the callback the point cloud is requested to be serialized into the binary buffer, which is then copied into the local buffer of the example:

```cpp
buffer.push_back(pc.Serialize());
```

As you can notice from the signature of `Serialize()` a const reference is returned meaning that internally no copy is performed for performance reasons.
Copying the data to the target location is the responsibility of the user, which in the example happens in the copy constructor of vector when calling `push_back()`.
Storing the information in a vector is just an example.
The buffer can be stored in a database, or streamed on a separate channel as well.

> Note: take care about timing. Writing to disk or databases is slow.
> Best practice is to copy the data into a buffer inside the callback and use a different thread to handle the data in the buffer.

Part 2 of the example demonstrate the deserialization.
First, the factory is used to create a point cloud object.
Secondly the binary buffer is provided to the point cloud object to populate its internals.
After this, the object is identical as it was passed to the point cloud callback:

```cpp
auto pc = xsdk::CreatePointCloud();
pc->Deserialize(buffer);
```

> Note: Deserialize will copy the bytes into internal memory.
> It is safe to discard the buffer after the call to `Deserialize()`.

> Note: If XenomatiX support ever requires point cloud data from the sensor, then simply dump the `Serialize()` output to a binary file and send that.
---

## Error handling

- **See**: [examples/errorHandling/Example_Error.cpp](errorHandling/Example_Error.cpp)
- **CMake target name**: `xavia::example_error`

This example show how to handle errors from the SDK.
There can be many reasons why an error is generated and some will be more critical than others.
Overall 2 variations are possible: synchronous and asynchronous errors.

- **Synchronous errors** are triggered by an action performed by the user.
  In other words: the exception happened during a call on one of the methods of the SDK objects.
  In this case the error will be communicated by throwing on exception of type `xavia::sdk::Error`,
  which derives from `std::exception`. Catch these errors using try/catch:
  
  ```c++
    try // try catch for synchronous errors
    {
      sensor->Start();
    }
    catch(const xavia::sdk::Exception& e)
    {
      //...
    }
  ```

- **Asynchronous errors** come directly from the sensor without being directly triggered by a user interaction.
  These are captured by the communication layer, translated into an object of type `xavia::sdk::Error`,
  and provided through an optional error callback that should be registered to the sensor factory.

  ```c++
  auto LocalErrorCallback = [](const xavia::sdk::Exception& e){ /*handle error */};
  factory.SetErrorCallback(LocalErrorCallback);
  ```

The `xavia::sdk::Error` objects derive from `std::exception` and therefore implement the `what()` function.
For localization and automation support capture the `xavia::sdk::Error` object directly and use the `GetType()` function.
This function will return an enum of type `xavia::sdk::SensorError`.
This enum contains many errors, including the events (see the [section on event handling](#event-handling)), so not all those values will be communicated through the error callback.

---

## Event handling

- **See**: [examples/eventHandling/Example_eventHandling.cpp](eventHandling/Example_eventHandling.cpp)
- **CMake target name**: `xavia::example_events`

The event handling example show how to attache a listener for events.
Events (or alerts) are critical situations that occur on the sensor.
In most (if not all) cases the sensor will not be functioning normal (or at all) for as long as the sensor is in this alerted state.
For example: When the `temperature_critical` alert is issued, some part of the sensor is overheating.
To protect against permanent damage, the sensor will either reduce its performance or stop completely.

Events come in two flavours:

- On/Off events change state when something happens (e.g. the temperature becomes critical and the temperature becomes normal again).
- One-off events, don't have these two states. (e.g. the shutdown event will shutdown the system. There is no coming back from this).

The event handler signature is:

```cpp
HandleAlert(const xm::Alert& alert, const std::optional<bool> flag);
```

- The first argument is the Alert type as an enum (see [api/alerts.h](../src/api/alerts.h)).
- The second argument is an optional flag. If present (check with `flag.has_value()`), it represents the On/Off state of the alert.

> Note: Events are only issued when the state changes. To understand the current state, query the sensor status.

---

## Logging

- **See**: [examples/logging/Example_logging.cpp](logging/Example_logging.cpp)
- **CMake target name**: `xavia::example_logging`

- **See**: [examples/fileLogging/Example_fileLogging.cpp](fileLogging/Example_fileLogging.cpp)
- **CMake target name**: `xavia::example_file_logging`

These examples demonstrates how to use the logging system to capture and handle
different levels of messages from the sensor. The logging system is essential
for monitoring sensor operations, diagnosing issues, and understanding the
runtime behavior of your application.

Both examples use the `SetLogCallBack` function to register a lambda that
receives the log severity and message string. This callback is invoked whenever
a log message is generated by the SDK or sensor. The message is then printed to
the console with its corresponding severity level.

```cpp
xsdk::SetLogCallBack([](const xsdk::Severity& sev, const std::wstring& msg)
{
  std::wcout << L"Log Received: " << xsdk::GetLogTypeString(sev) << L": " << msg << std::endl;
});
```

This approach allows you to fine-tune the verbosity of logs during development
or troubleshooting, while keeping production builds clean and performant.

```cpp
xsdk::SetLogSeverity(xsdk::Severity::XM_INFO);
```

After running the sensor for 5 seconds, the example stops the sensor and exits
cleanly.

The `fileLogging` examples does the same thing, but it activates logging to an SDK controlled file.
This is useful if you whish to share this file with XenomatiX support.

If you so desire, you can add extra log messages to the SDK log using `xavia::sdk::Log` function as demonstrated in the examples.
If used extensively we advise to create macro's as such:

```c++
#define LOG_INFO(x) xavia::sdk::Log(xavia::sdk::Severity::XM_INFO, x, __FILE__, __LINE__, __FUNCTION__);
```

With these two approaches the SDK supports both use cases:

- You can use the Logging system of the SDK and attach your own logs to it.
- You can extract all the logs in the callback and integrate them into your own logging system.

> Note: The logging system is designed to be non-blocking and efficient. It
supports multiple log levels, and you can filter them at runtime to avoid
performance overhead or excessive output.

> Note: The `SetLogOutputFolder` should always be called first if you desire to use it.
> it will initialize the logging and this can only happen once.

---

## Query information

- **See**: [examples/query/Example_query.cpp](query/Example_query.cpp)
- **CMake target name**: `xavia::example_query`

The query example demonstrates how to retrieve and display sensor information using the SDK.
This includes fetching the sensor's serial number, current operational state, and any active errors.

The example also shows how to check the sensor lifecycle:

- Getting the state of the sensor before starting it.
- Starting the sensor and checking its state.
- Stopping the sensor and verifying the final state.

This approach provides a comprehensive overview of the sensor's status, which is essential for monitoring and diagnostics
in production environments.

It is good practice to check the sensor state when first connecting to it in order to synchronize with the condition of
the sensor from previous usage or other clients.

It is important to realize that not all query functions actually query the sensor.
Some will fetch information only from the internal cache of the SDK which is kept up to date by the SDK itself.
The function GetErrors() will initiate a full synchronization of serial number, state and current error list of
the sensor. The Functions GetSerialNumber() and GetCurrentState() retrieve information from cache. This cache is
created on first connection and updated when events are broadcast by the sensor.
The consequence is that GetErrors() will take more time to return than GetSerialNumber() and GetCurrentState().

In production, it is required to surround query calls (specifically GetErrors()) with try catch statements.
Queries can throw exceptions if a) the sensor responds with an error, or b) if the sensor does not respond at all and
the command times out.

Note that in many scenarios it is not required to query the sensor errors regularly.
Any change in state or change in error will be reported to the event callback as well when it occurs.
See [the events example](#event-handling).

### Possible errors returned by `GetErrors()`

The `GetErrors()` function returns a vector of `xavia::sdk::SensorError` enum values that indicate any problems with the sensor. These errors can be checked to understand the current state of the sensor:

- `POWER_CRITICAL`: Critical power issue detected.
- `TEMPERATURE_CRITICAL`: Critical temperature issue detected.
- `EYE_SAFETY_ALERT`: Eye safety alert triggered (e.g., potential exposure to laser).
- `HUMIDITY_ALERT`: Humidity levels outside of normal operating range.
- `VOLTAGE_ALERT`: Voltage levels outside of normal operating range.
- `HOUSING_REMOVED`: The sensor housing has been removed.
- `ENGINEERING_BYPASS_PRESENT`: Engineering bypass mode is active.
- `PAST_ERROR`: A past error that may still be relevant.

For more information on these errors and their meaning, please read the user manual of the sensor.

---

## System power off

- **See**: [examples/poweroff/Example_poweroff.cpp](poweroff/Example_poweroff.cpp)
- **CMake target name**: `xavia::example_poweroff`

The SDK also allows to power off or reboot the sensor.
For this first use the factory to create a sensor object.
Then call the `Poweroff()` or `Reboot()` method.

Both methods will transmit a TCP message to the sensor.
The sensor will either respond with the message that the command has been queued or that the queue is full.
Unless an exception is thrown by the functions, you should assume that the sensor will power off soon.

Do not continue using the `sensor` object after sending either command.
Soon after it will lose connection to the sensor.
Dispose of the object, wait for the reboot and then attempt to create a new sensor object using the same factory.
Creating a new sensor object will fail until the sensor is fully rebooted and ready for operation.

---

## Camera Examples

A series of example focused on usage of the add-on camera for Xavia 6D.
Requires Xavia SDK v2.1.0 or higher.
Only supported on windows and linux x86_64.
The full manual on the camera add on can be found in [ManualCamera.md](../doc/UserManuals/ManualCamera.md)

### Camera basic

- **See**: [examples/cameraBasic/cameraBasic.cpp](cameraBasic/cameraBasic.cpp)
- **CMake target name**: `xavia::example_camera_basic`

This example demonstrates how to use the camera feature with the Xavia SDK.
The camera support allows you to capture RGB images from the attachment camera of Xavia 6D.

The example shows how to setup the camera using the `SensorFactory` and how to access the camera object:

```c++
xsdk::SensorFactory factory;
factory.SetImageCallback(imageCallback); //important

auto sensor = factory.Build();
auto camera = sensor->GetCamera();
```

The presence of an image callback is required to enable the camera feature. If not, then the SDK will not attempt to connect to the camera.

There is limited configuration required on the camera.
During the `sensorFactory` phase you can use `SetCameraIPAddress` if the camera does not use the default IP address (10.10.100.12).
Additionally you can use `SetImageBufferSize` to change the internal buffer size of SDK for the camera stream.
The default value is 5 images.
Larger buffer sizes can help cope with variations in processing of the images, but will result in more memory usage and stale data.

Creating the camera object goes together with creating the sensor object.
The sensor object will hold on to the camera object and their lifetimes are connected.
The camera object will be automatically cleaned up together with the sensor object, unless you keep the shared_ptr around for longer.
If the camera object creation fails, errors will be logged in the log stream.
Additionally, the return from `GetCamera` will be a null.

Starting the camera stream to the SDK is done separately from the point cloud stream.
For example:

```cpp
auto sensor = factory.Build();
auto camera = sensor->GetCamera();
camera->Start();
```

And similar for stopping it.

The image callback will receive a unique pointer to a `xavia::sdk::IImage` object.
This behavior is similar to point cloud handling.
The image data is yours to have in that scope and you are allowed to move the data into other threads.
Each image has metadata (counters, timestamp, ...) and image data
In this example, the metadata is accessed to retrieve the frameNumber and image dimensions.
Then the image data is accessed as demonstration.

---

1. **Camera configuration** via `SensorFactory`:

   ```cpp
   xm::SensorFactory factory;
   factory.SetCameraIPAddress("10.10.100.20");
   factory.SetImageCallback(imageCallback);
   ```

2. **Accessing the camera** from the sensor object:

   ```cpp
   auto sensor = factory.Build();
   auto camera = sensor->GetCamera();  // Returns nullptr if not configured
   ```

3. **Independent camera control**:

   ```cpp
   camera->SetExposureTimeUs(5000);  // Set exposure to 5ms
   camera->Start();                   // Start camera streaming
   // Camera runs independently from lidar sensor
   camera->Stop();                    // Stop camera streaming
   ```

4. **Processing images** via callback:
   
   ```cpp
   void imageCallback(std::unique_ptr<xm::IImage> image) {
       const auto& metadata = image->GetMetaData();
       std::cout << "Image: " << metadata.width << "x" << metadata.height 
                 << " Frame #" << metadata.frameNumber << std::endl;
       
       // Access pixel data
       const auto& pixels = image->GetPixelData();
       // Process image...
   }
   ```

---

### Recording camera images as jpg

- **See**: [examples/cameraRecord8/Example_camera.cpp](cameraRecord8/Example_camera.cpp)
- **CMake target name**: `xavia::example_camera_jpg8`

- **See**: [examples/cameraRecord12/Example_camera.cpp](cameraRecord12/Example_camera.cpp)
- **CMake target name**: `xavia::example_camera_jpg12`

- **See**: [examples/cameraRecordBayerRG12AsJPG/Example_camera.cpp](cameraRecordBayerRG12AsJPG/Example_camera.cpp)
- **CMake target name**: `xavia::xsdk_example_camera_recB12`

- **See**: [examples/cameraRecordBayerRG12pAsJPG/Example_camera.cpp](cameraRecordBayerRG12pAsJPG/Example_camera.cpp)
- **CMake target name**: `xavia::xsdk_example_camera_recB12p`

These showcase how to set up the camera in a certain image format and then store the images as jpeg.
In all cases, the jpeg images are stored as RGB8.

The general format of the examples is:

```c++
int main(int argc, char** argv)
{
  xsdk::SensorFactory factory;
    
  // step 1. Set up image callbacks
  factory.SetImageCallback(imageCallback); // required for camera support

  // step 2. Configure the camera to output 8 bit images
  factory.SetImageOutputFormat(xavia::sdk::PixelFormat::RGB8); // format varies per example
    
    // step 3. Build the sensor and get the camera
    auto sensor = factory.Build();
    auto camera = sensor->GetCamera();
    
    // start and stop the camera ...
}

void imageCallback(std::unique_ptr<xsdk::IImage> image)
{
  // step 5. Process incoming image
  const auto& metadata = image->GetMetaData();
  std::wcout << L"Image #" << metadata.frameNumber;
  
  try
  {
    image->SaveAsJpeg(L"image_" + std::to_wstring(metadata.frameNumber) + L"_rgb8.jpg", 90, true); 
    std::wcout << L" - saved successfully." << std::endl;
  }
  catch(const std::exception& e)
  {
    std::wcerr << L" - ERROR! Failed to save image as jpeg: " << e.what() << std::endl;
  }
}
```

Note that these are naive implementations that assume the computer is powerful enough to keep up with 20Hz jpg compression.

---

### Combining Lidar Camera example

- **See**: [examples/combinedLidarCamera/Example_camera.cpp](combinedLidarCamera/Example_camera.cpp)
- **CMake target name**: `xavia::example_combined_lidar_camera`

This example shows how to use camera and lidar together:

```cpp
// Configure both
factory.SetPointCloudCallback(pointCloudCallback);
factory.SetCameraIPAddress("10.10.100.20");
factory.SetImageCallback(imageCallback);

auto sensor = factory.Build();
auto camera = sensor->GetCamera();

// Start both independently
sensor->Start();   // Start lidar
camera->Start();   // Start camera

// Both run concurrently, delivering data via callbacks

// Stop both
camera->Stop();
sensor->Stop();
```

It defines two callbacks, one for each data type.
Then it starts both camera and sensor at the same time.

### Combined recorder

- **See**: [examples/combinedLidarCameraRecorder/Example_camera.cpp](combinedLidarCameraRecorder/Example_camera.cpp)
- **CMake target name**: `xavia::xsdk_example_combined_lidar_camera_recorder`

This elaborate example shows a combination of lidar and camera recording.

It builds on the previous examples by combining them.
The major difference with the [combined lidar camera example](#combining-lidar-camera-example) is in the callbacks which now store the data:

```c++
void pointCloudCallback(std::unique_ptr<xsdk::IPointCloud> pointCloud)
{
  auto md = pointCloud->GetMetaData();
  if(md.lineNumber == 0)
  {
    SavePointCloud();
    pointClouds.clear(); // clear stored point clouds after saving
  }
  pointClouds.push_back(std::move(pointCloud)); // aggregate the point cloud lines
}

void imageCallback(std::unique_ptr<xsdk::IImage> image)
{
  const auto& metadata = image->GetMetaData();
  image->SaveAsJpeg(L"image_" + std::to_wstring(metadata.frameNumber) + L".jpg", 90, true);
}
```

In a production version of this, the data handling would be outsourced to separate, specialized objects.
Potentially parallel, multi threaded data writing is used to preserve all data.