Example: Util ComputedValues

This examples dmonstrates how to use the computedvalue utility module for creating average or mojority votes.

When measuring physical “stuff”, things are not always precise. Binary signals - e.g. valve feedbacks

or switches - tend to oscillate before they settle. Pressure values from non centrifugal pumps have distinct phases. To handle these sort of values, we need to average the signals out… which is such a common operation that CENA provides you with classes that behave just like the native counterparts (adding, comparing, etc.), but auto-average on assignments.

#include <iostream>

Overview

Here are two computed value classes.

#include "computedvalue/MovingWindowAverage.hpp"
#include "computedvalue/MajorityVote.hpp"

namespace cena = semodia::controlengine::native;
namespace computedvalue = cena::util::computedvalue;

Each computed value is essentially a fixed size list of values. To improve performance, both of the above classes preallocate a vector to minimize lookup times and allocations at runtime. The vector is preinitialized with a default value.

@startuml
    class AbstractMovingWindow <type>{
        #window : vector<type>
        #windowPosition : unsigned number

        +getValue() : <type>
    }

    class MovingWindowAverage<numeric> extends AbstractMovingWindow {
        +getValue() : <type>
    }
    note left of MovingWindowAverage::getValue
        Averages window as
        numeric values
    end note

    class MajorityVote<bool> extends AbstractMovingWindow {
        +getValue() : <type>
    }
    note right of MajorityVote::getValue
        Counts the majority
        of booleans
    end note
@enduml

What differs is how the window is interpreted in getValue().

Examples

Let’s take a look at two examples

Numeric averages

In our fictitious example we are measuring the pressure of a membrane pump. We want to create a pressure average and use that as a “normal” floating point value.

First, we will create a new pressure value, like so

void numericAverageExample()
{
    computedvalue::MovingWindowAverage<float> pressure(
            4, // Window size: averages 4 values
            2  // Initial value
    );

Since we have not sampled anything yet, our pressure would report its initial value.

This initial value is not part of the average; the “2” will disappear as soon as we enter a proper value. So… how do we add numbers to the average? Simply assign them, like this:

pressure = 10;
pressure = 12;
pressure = 9;

Our window holds the value 10,12 & 9. The average should be 31/3 ~ 10.333.

std::cout << "The current average pressure is " << pressure << std::endl;

If we add new values, the window will of course move…

pressure = 12;
pressure = 14;
pressure = 16;

Remember that the window size we chose was 4; so our window holds the value 9, 12, 14, 16.

std::cout << "The current average pressure is " << pressure << std::endl;

You can use the averaged value in normal calculations and assignments as you would with any other numeric type:

    // float currentPressure = pressure;
    // float pressureLargerThanCurrent = pressure + 10;

    pressure += 10; // This would add avg(pressure) + 10 to our window
    return;
}

Time bounded averages

You want an average value over a specific time, e.g. 1s?

Simply wrap the sampling - i.e. the “pressure = …” - into a FrequencyLimitedTaskLoopTask; take a look at the task tutorial for more info on that.

By running the sampling tasks once every 100ms and creating an average value with a windowsize of 10, you get an averaged value over the last second.

Logic averages

A classic: You want to debounce a binary input signal, like the open/closed-feedback of a valve? This works pretty much like the numeric example above… simply add the sampled values to a MajorityVote

void booleanAverageExample()
{
    computedvalue::MajorityVote valveOpen(
            4, // Window size: averages 4 values
            false // Initial value
    );

In a MajorityVote, the initial values is used both for initial reporting, but it also settles ties in windows of even size.

std::cout << "The valve is open? " << valveOpen << std::endl; // initial value: false

valveOpen = false;
valveOpen = true;

std::cout << "The valve is open? " << valveOpen << std::endl; // initial value used to settle tie

The rest works as above, including logical operations like & or |

    valveOpen = !valveOpen; // adds true, as valveOpen currently results in false
    std::cout << "The valve is open? " << valveOpen << std::endl; // initial value used to settle tie

    valveOpen &= true; // true & true --> adds true
    valveOpen |= false; // true | false --> adds true
    // etc.

    return;
}

int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
    numericAverageExample();
    booleanAverageExample();
    return 0;
}

Source Code

Here’s the complete source code from this example:

 1#include <iostream>
 2
 3#include "computedvalue/MovingWindowAverage.hpp"
 4#include "computedvalue/MajorityVote.hpp"
 5
 6namespace cena = semodia::controlengine::native;
 7namespace computedvalue = cena::util::computedvalue;
 8
 9void numericAverageExample()
10{
11    computedvalue::MovingWindowAverage<float> pressure(
12            4, // Window size: averages 4 values
13            2  // Initial value
14    );
15
16    pressure = 10;
17    pressure = 12;
18    pressure = 9;
19
20    std::cout << "The current average pressure is " << pressure << std::endl;
21
22    pressure = 12;
23    pressure = 14;
24    pressure = 16;
25
26    std::cout << "The current average pressure is " << pressure << std::endl;
27
28    // float currentPressure = pressure;
29    // float pressureLargerThanCurrent = pressure + 10;
30
31    pressure += 10; // This would add avg(pressure) + 10 to our window
32    return;
33}
34
35void booleanAverageExample()
36{
37    computedvalue::MajorityVote valveOpen(
38            4, // Window size: averages 4 values
39            false // Initial value
40    );
41
42    std::cout << "The valve is open? " << valveOpen << std::endl; // initial value: false
43
44    valveOpen = false;
45    valveOpen = true;
46
47    std::cout << "The valve is open? " << valveOpen << std::endl; // initial value used to settle tie
48
49    valveOpen = !valveOpen; // adds true, as valveOpen currently results in false
50    std::cout << "The valve is open? " << valveOpen << std::endl; // initial value used to settle tie
51
52    valveOpen &= true; // true & true --> adds true
53    valveOpen |= false; // true | false --> adds true
54    // etc.
55
56    return;
57}
58
59int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
60{
61    numericAverageExample();
62    booleanAverageExample();
63    return 0;
64}