Documentation

This page hosts the documentation of the SmartCGMS software architecture and all of its components.


Approximator

An approximator is an entity, that processes the incoming discrete signal and approximates/interpolates the values with a continuous curve.

Descriptor

Every approximator is described with by its descriptor (scgms::TApprox_Descriptor):

struct TApprox_Descriptor {
    const GUID id;
    const wchar_t *description;
};

The descriptor fields are self-explanatory - the id field contains a unique GUID identifier of the approximator entity and description contains the approximator entity name, represented as a null-terminated string (must not be nullptr).

A shared object implementing an approximator must:

  • export a do_get_approximator_descriptors function
    • this function returns a continuous array of approximator descriptors identified by its first and one-after-last element
  • export a do_create_approximator function, and create an approximator contained within that array when called

An example of do_get_approximator_descriptors may be as follows:

const std::array<scgms::TApprox_Descriptor, 3> approx_descriptions = { { ApproxDescriptor_1, ApproxDescriptor_2 } };

extern "C" HRESULT IfaceCalling do_get_approximator_descriptors(scgms::TApprox_Descriptor **begin, scgms::TApprox_Descriptor **end) {
    *begin = const_cast<scgms::TApprox_Descriptor*>(approx_descriptions.data());
    *end = *begin + approx_descriptions.size();
    return S_OK;
}

An example of do_create_approximator may be as follows:

extern "C" HRESULT IfaceCalling do_create_approximator(const GUID *approx_id, scgms::ISignal *signal, scgms::IApproximator **approx) {
    if (*approx_id == ApproxDescriptor_1.id)
        return Manufacture_Object<CExample_Approximator>(approx, scgms::WSignal{ signal });

    return E_NOTIMPL;
}

Interface

Every approximator must implement a interface, which is defined as follows:

class IApproximator : public virtual refcnt::IReferenced {
    public:
        virtual HRESULT IfaceCalling GetLevels(const double* times, double* const levels, const size_t count, const size_t derivation_order) = 0;
};

The sole interface method must return approximated curve (or its derivation, the order is given by the derivation_order parameter) in requested times given by the times array. The count of the array elements is determined by the count parameter value. The method fills the levels array with either approximated levels or a NaN value (e.g.; std::numeric_limits<double>::quiet_NaN() in C++), when it is not able to perform approximation within given bounds.

The method is allowed to return E_INVALIDARG if the requested derivation order could not be calculated. The approximator must always support the derivation_order = 0.

Note that the approximator object retains a reference to an original signal (as a scgms::WSignal weak reference) in its constructor. The object is not notified in any way when the signal is updated with new values. To ensure the approximator works with recent data, an update of internal state should be issued every time the GetLevels method gets called.

Approximator flow

Above you can find the diagram depicting how the signal uses the approximator instance to approximate its discrete levels. It is common, that between the second and third flow entry, the approximator calls Get_Discrete_Bounds and Get_Discrete_Levels on the signal object in order to obtain the current discrete values of the signal.

Example implementation

As an example, let us implement a simple "step" approximator, that outputs the most recent value to the requested time. It is a very simple example, that has very little use and serves just as a demonstration.

Similarly to other entities, let us create a descriptor header file, despite containing solely the GUID of our approximator:

#include <rtl/guid.h>

namespace example_approx {
    constexpr GUID id = { 0xf9b90774, 0x9326, 0x4513, { 0xbb, 0x5e, 0xa2, 0x49, 0xea, 0x70, 0xdb, 0x71 } }; // {F9B90774-9326-4513-BB5E-A249EA70DB71}
};

Then we define and implement the approximator class, complying to the scgms::IApproximator interface. Note, that we are always retrieving the signal values in the GetLevels call - that could potentially be a performance drawback:

class CExample_Approximator : public virtual scgms::IApproximator, public virtual refcnt::CReferenced {

    private:
        scgms::WSignal mSignal;

    public:
        CExample_Approximator(scgms::WSignal signal): mSignal(signal) {
        }

        virtual ~CExample_Approximator() {};

        virtual HRESULT IfaceCalling GetLevels(const double* times, double* const levels, const size_t count, const size_t derivation_order) override {

            if (derivation_order != 0)
                return E_INVALIDARG;

            size_t levels_count;
            if (mSignal.Get_Discrete_Bounds(nullptr, nullptr, &levels_count) != S_OK)
                return E_FAIL;

            // check that we have some signal values; this should never happen, as the GetLevels is called only when some values are already available
            if (levels_count == 0)
                return E_FAIL;

            std::vector<double> signal_times;
            std::vector<double> signal_levels;

            size_t filled;
            if (mSignal.Get_Discrete_Levels(signal_times.data(), signal_levels.data(), levels_count, &filled) != S_OK)
                return E_FAIL;

            // fill all requested values
            for (size_t i = 0; i < count; i++) {

                // we cannot fill earlier values
                if (times[i] < signal_times[0])
                    levels[i] = std::numeric_limits::quiet_NaN();
                else
                {
                    // for every requested time within bounds, let us find the closest earlier signal value

                    for (size_t j = 0; j < levels_count; j++) {
                        if (signal_times[j] > times[i] || j == levels_count - 1) {
                            levels[i] = signal_levels[j - 1];
                        }
                    }
                }

            }

            return S_OK;
        }
}

Then, we define a descriptor block:

namespace example_approx {	
    const scgms::TApprox_Descriptor ExampleApprox_Descriptor = {
        id,
        L"Example step approximator"
    };
}

We have to implement and export the do_get_approximator_descriptors to expose implemented metric:

const std::array<scgms::TApprox_Descriptor, 1> approx_descriptions = { { example_approx::ExampleApprox_Descriptor } };

HRESULT IfaceCalling do_get_approximator_descriptors(scgms::TApprox_Descriptor const **begin, scgms::TApprox_Descriptor const **end) {
    return do_get_descriptors(approx_descriptors, begin, end);
}

And as a last step, implement and export the do_create_approximator factory function:

extern "C" HRESULT IfaceCalling do_create_approximator(const GUID *approx_id, scgms::ISignal *signal, scgms::IApproximator **approx) {

    if (*approx_id == example_approx::ExampleApprox_Descriptor.id)
        return Manufacture_Object<CExample_Approximator>(approx, scgms::WSignal{ signal });

    return E_NOTIMPL;
}