How to configure ODS

A basic JSON

The configuration example uses the following JSON for a simple configuration. This configuration expects at least one Head (3D imager) at port 2.

{
  "applications": {
    "instances": {
      "app0": {
        "class": "ods",
        "configuration": {
          "zones": {
            "zoneConfigID": 0,
            "zoneCoordinates": [
              [
                [
                  0.0,
                  -1
                ],
                [
                  1.0,
                  -1
                ],
                [
                  1.0,
                  1
                ],
                [
                  0.0,
                  1
                ]
              ],
              [
                [
                  1.0,
                  -1
                ],
                [
                  2.0,
                  -1
                ],
                [
                  2.0,
                  1
                ],
                [
                  1.0,
                  1
                ]
              ],
              [
                [
                  2.0,
                  -1
                ],
                [
                  3.0,
                  -1
                ],
                [
                  3.0,
                  1
                ],
                [
                  2.0,
                  1
                ]
              ]
            ]
          }
        },
      "name": "app0",
      "ports": [
        "port2",
        "port6"
      ],
      "state": "CONF"
    }
  }
}
}

Note

port6 is always part of the “ports” configuration. It refers to the IMU and is needed for the ODS algorithm to work properly.

JSON validation

This example show how to use the nlohmann_json_schema_validator package to validate configurations before setting them. Validating the configuration is something that is done under-the-hood by the O3R before the configuration is set, but it does not provide a verbose error handling. The nlohmann_json_schema_validator package will tell the user exactly where the error is in the provided JSON. It also allows us to display the use of the O3R schema.

The dependency to nlohmann_json_schema_validator is set as optional in the examples, and if the package is not found on the user’s machine, the validation will be ignored. To enable the use of this package, install it following the instructions here. Note that you will also need to install or upgrade the package nlohmann-json to version >=3.8, as it is used as a dependency by nlohmann_json_schema_validator.

The configuration example

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 << "This was expected. Continuing on with the tutorial." << 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);
    
    std::clog << "Setting test configurations:" << std::endl;
    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 (...) {
        std::clog << "Error caught while configuring from a non-existent file.\n"
                  << "This is expected, continuing with the example.";
                }

    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>
#ifdef USE_JSONSCHEMA
    #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/common/json.hpp>

    #include <ifm3d/device/o3r.h>
    #include <ifm3d/device/err.h>
    class Validator {
    public:
        ifm3d::O3R::Ptr o3r;
        ifm3d::json o3r_schema;
        Validator(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)));
                std::clog << "Successful JSON validation."  << std::endl;
            }
            catch (const std::exception &e) {
                std::cerr << "Validation failed: "
                        << 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);
            }
        }
    };

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

    class Validator {
    public:
        ifm3d::O3R::Ptr o3r;

        Validator(ifm3d::O3R::Ptr o3r_) : o3r(o3r_) {
            std::clog << "JSON validation unavailable: missing dependency." << std::endl;
        }
        void ValidateJson(ifm3d::json config) {
            std::clog << "JSON validation unavailable." << std::endl;
        }
    };
#endif


using namespace ifm3d::literals;

class ODSConfig {
public:
    ifm3d::O3R::Ptr o3r;
    Validator val;

    ODSConfig(ifm3d::O3R::Ptr o3r_) : o3r(o3r_), val(o3r) {
    }

    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 {
            val.ValidateJson(ifm3d::json::parse(config_str));
            o3r->Set(ifm3d::json::parse(config_str));
            std::clog << "Successfully set the configuration." << std::endl;
        }
        catch (const std::exception &e) {
            std::cerr << "Caught exception while configuring: "
                      << e.what()
                      << std::endl;
        }
    }
};