How to stream ODS data

ods_config.cpp
/*
 * Copyright 2021-present ifm electronic, gmbh
 * SPDX-License-Identifier: Apache-2.0
 */

// This example showcases how to configure an application
// using the ifm3d API and provides utilities for verbose
// reporting of JSON syntax errors.
#include <cstdlib>
#include <iostream>
#include "ods_config.h"

#include <ifm3d/device/o3r.h>
#include <ifm3d/device/err.h>

int main()
{
    ///////////////////////////////////////////////////
    // Variables needed for the example
    ///////////////////////////////////////////////////
    std::string config_extrinsic_path = "../Configs/extrinsic_one_head.json";
    std::string config_app_path = "../Configs/ods_one_head_config.json";

    // Get the IP address from the environment variable
    // If not defined, use the default IP
    const char* IP = std::getenv("IFM3D_IP");
    if (!IP) {
        IP = ifm3d::DEFAULT_IP.c_str();
        std::clog << "Using default IP" << std::endl;
    }
    std::clog << "IP: " << IP << std::endl;

    // Declare the device object (one object only, corresponding to the VPU)
    auto o3r = std::make_shared<ifm3d::O3R>(IP);

    ///////////////////////////////////////////////
    // Examples of getting configuration snippets
    ///////////////////////////////////////////////
    // Here we expect a fully booted system.
    // Get the full configuration
    std::clog << "Getting full config" << std::endl;
    std::clog << std::setw(4) << o3r->Get() << std::endl;

    // Get a subset of the configuration
    std::clog << "Getting partial config" << std::endl;
    std::clog << std::setw(4) << o3r->Get({"/device/swVersion/firmware"}) << std::endl;

    // Get multiple subsets of the configuration
    std::clog << "Getting multiple partial configs" << std::endl;
    std::clog << std::setw(4) << o3r->Get({"/device/swVersion/firmware",
                                            "/device/status",
                                            "/ports/port0/info"}) << std::endl;

    // Throw an exception if retrieving config in the wrong path
    std::clog << "Getting config for wrong path" << std::endl;
    try {
        o3r->Get({"/device/wrongKey"});
    }
    catch (const ifm3d::Error& ex) {
        std::clog << "Caught exception: " << ex.message() << std::endl;
    }

    std::clog << "Finished getting configurations" << std::endl;

    ///////////////////////////////////////////////
    // Examples of setting configuration snippets
    ///////////////////////////////////////////////
    // We use a custom class to provide configuration
    // facilities and additional error handling.
    // The user could directly use the ifm3d library
    // native calls to set the configuration.
    ODSConfig configurator(o3r);

    configurator.SetConfigFromStr(R"(
        {"device": { "info": { "description": "I will use this O3R to change the world"}}})");

    // Set two configurations at the same time
    configurator.SetConfigFromStr(R"(
        {"device": {
            "info": {
                "name": "my_favorite_o3r"
            }
        },
        "ports": {
            "port0": {
                "info": {
                    "name": "my_favorite_port"
                }
            }
        }
        }
    )");

    // We expect that this example is run from the Cpp/Examples/build folder.
    // Update the path to the config files if using a different setup.
    configurator.SetConfigFromFile(config_extrinsic_path);
    configurator.SetConfigFromFile(config_app_path);

    try {
        configurator.SetConfigFromFile("/non/existent/file.json");
    }
    catch (...) {
        // Failing silently to continue with the tutorial
    }

    std::clog << "You are done with the configuration tutorial!" << std::endl;

    return 0;
}
ods_config.h
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>

#include <nlohmann/json-schema.hpp>
#include <nlohmann/json.hpp> // nlohmann json needs to be included before any ifm3d include.

#define IFM3D_JSON_NLOHMANN_COMPAT // enable the nlohmann json converter
#include <ifm3d/device/json.hpp>

#include <ifm3d/device/o3r.h>
#include <ifm3d/device/err.h>

using namespace ifm3d::literals;

class ODSConfig {
public:
    ifm3d::O3R::Ptr o3r;
    ifm3d::json o3r_schema;

    ODSConfig(ifm3d::O3R::Ptr o3r_) : o3r(o3r_) {
        o3r_schema = o3r->GetSchema();
        try {
            validator.set_root_schema(nlohmann::json::parse(o3r_schema.dump(0)));
        }
        catch (const std::exception &e) {
            std::cerr << "Validation of schema failed: "
                      << e.what()
                      << std::endl;
        }
    }

    void ValidateJson(ifm3d::json config) {
        // This validation function will provide verbose
        // errors when the provided json configuration 
        // is wrong.
        try {
            validator.validate(nlohmann::json::parse(config.dump(0)));
        }
        catch (const std::exception &e) {
            std::cerr << "Validation failed: "
                      << e.what()
                      << std::endl;
        }
    }

    void SetConfigFromFile(const std::string& config_path) {
        // Reading a configuration from file and setting it
        std::clog << "Reading configuration file at: " 
                  << config_path
                  << std::endl;
        std::ifstream config_file;
        config_file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        std::stringstream config_buffer;
        try {
            config_file.open(config_path);
            if (config_file.is_open()) {
                config_buffer << config_file.rdbuf();
            }
            SetConfigFromStr(config_buffer.str());
        }
        catch (const std::ifstream::failure& e) {
            std::cerr << "Caught exception while reading configuration file: " 
                      << e.what() 
                      << std::endl;
        }
        catch (...) {
            std::cerr << "Unknown error while reading configuration file."
                      << std::endl;
        }
    }

    void SetConfigFromStr(const std::string& config_str) {
        try {
            ValidateJson(ifm3d::json::parse(config_str));
            o3r->Set(ifm3d::json::parse(config_str));
        }
        catch (const std::exception &e) {
            std::cerr << "Caught exception while configuring: "
                      << e.what()
                      << std::endl;
        }
    }

private:
    nlohmann::json_schema::json_validator validator{ nullptr, CheckJSONFormat };

    static void CheckJSONFormat(const std::string& format, const std::string& value) {
        // This is necessary because "format" is a keyword both used internally by the schema
        // validator and in the O3R schema.
        if (format == "ipv4") {
            std::clog << "IPV4 formatting";
            // if (!std::get<0>(ifm::eucco::network::normalizeIPv4Address(QString::fromStdString(value))))
            // {
            //     throw std::invalid_argument("unknown ip format");
            // }
        }
        else {
            throw std::logic_error("Don't know how to validate " + format);
        }
    }
};