0

This seems like a fundamental that I've completely forgotten from comp sci 20 years ago.

How would I link/trigger an object function from another object. Or what is the name of the concept I'm trying to find? (c++ if it matters)

My example is I have a sensor which has accumulators. The accumulators have an array of time interval readings. They accumulate values until their interval is complete and add that to the readings array.

I wanted to make the accumulators flexible, different intervals, timeframes, or the amount of accumulators for each sensor. Eg. 1 hour with 1 min intervals, 1 day with hourly...

I have a class for the Sensor, and Accumulator. But how do I declare multiple accumulators and allow smaller timeframe readings to bubble up to the next higher readings?

==========================

To add a more concrete example because I'm struggling to explain clearly...

Example function in sensor class: Sensor.addAccumulator(timeframe, interval)

So first timeframe could be a 1 hour timeframe and 1 min interval. Then another accumulator added with a 24 hour timeframe and 30 min interval. Next 1 week with 12 hour interval.

The sensor would has a function to get the current reading then gives it to accumulator to process.

But when 1 min of data was accumulated, how can I give that to the next higher timeframe?

I could just give the readings to every accumulator but the calculations become redundant and it's on a microcontroller.

=========

If I had a high powered device I'd dump the values in a time series database with data retention policies that downsample older data.

Joshua Jenkins
  • 315
  • 1
  • 15
  • so the "Accumulators" are declared as member elements of the "Sensor"? – user56983 Jul 27 '21 at 23:30
  • and then the "Accumulator" has its own definition as a class? – user56983 Jul 27 '21 at 23:32
  • 1
    Maybe we can tag this as "C++" if you want to post a snippet. – user56983 Jul 27 '21 at 23:33
  • Yes, I created a sensor class which has a function to get a realtime reading from the sensor and gives it to the accumulator class to do the calculations on. – Joshua Jenkins Jul 27 '21 at 23:40
  • I only have one accumulator right now because I don't know how to link a higher level one. Basically the higher level ones are downsampling the data but I'm trying to avoid redundant recalculations of realtime data for every time frame. – Joshua Jenkins Jul 27 '21 at 23:43
  • You could be looking for "Callback". If not, I'm not sure what you are asking – user4581301 Jul 27 '21 at 23:44
  • I'm just gonna throw in an answer. – user56983 Jul 27 '21 at 23:51
  • I'm having trouble describing what I'm asking. I'll add a more specific example. – Joshua Jenkins Jul 27 '21 at 23:52
  • I'mma post it anyway and you decide later. – user56983 Jul 27 '21 at 23:52
  • Hey cool, I didn't know you were tinkering around with hardware too. I never got into any of that. – user56983 Jul 28 '21 at 00:08
  • One difficulty here is that, as you say, you want to make the accumulators flexible — but that means you can't necessarily implement them as a hierarchy. Imagine you add two accumulators, one with a 2-minute interval, another with a 5-minute interval. The second accumulator can't draw its data from the first, the resolution is wrong. So if you're going to permit that, you may have to treat each accumulator as wholly separate and sacrifice the efficiency of chaining them. – FeRD Sep 08 '21 at 14:34

2 Answers2

1

What you needed to do was to have a linked list for the accumulators.

So then you'd have your class Sensor but as a member you'd have an element

mll_Accumulator *accumulator;

And what that is is the first element of the linked list. To make a linked list, recall, you have to have a type declaration that had an element that was a pointer to an instance of an object of the same type.

And then if you have mll_Accumulator *next;, then to access that through a linked list that starts with mll_Accumulator seed = new mll_Accumulator;, you would access it by dereferencing the member element with seed->next or iterate through with seed->next->next, and you would lengthen the linked list by calling, or perhaps iterating through the list to the end where seed->next is null and calling seed->next = new mll_Accumulator;, because otherwise you could be looking at arbitrary memory locations.

and I was thinking mll_- as a naming convention for a prefix could be read as "member linked list". But that's probably what it was: a linked list.

user56983
  • 111
  • 6
  • 1
    Side note: remember that C++ has a `std::list` (defined such that the only straight forward way to implement it is a doubly linked list) and `std::forward_list` (almost certainly a singly linked list) built-in. It's pretty rare to need to roll-your-own linked list. That said, a `std::deque` might be a better datatype for the job described by this answer. – user4581301 Jul 28 '21 at 00:43
  • @user4581301 There's always space for another answer. – user56983 Jul 28 '21 at 00:44
  • I'm not sure enough about what the question is to write an answer. I just wanted to point out that what you're suggesting isn't as hard to implement as it sounds. The tools are already there. – user4581301 Jul 28 '21 at 00:48
0

I expressed my concerns about the flexibility of the Accumulator design in a comment, but setting that aside — if you do want to implement hierarchical Accumulators, I think in terms of OOP the best option would be to flip your classes around — don't have a Sensor that holds Acccumulators, but rather have Accumulators that hold an abstract DataSource — that DataSource could either be the Sensor itself, or another Accumulator.

The DataSource interface provides a .getValues(size_t count) function, which for the Sensor would only support .getValues(1) to read the hardware and returns the measurement, and for an Accumulator reads that many stored values and returns them. (So you'd need a std::vector<ValueType> as the return type, even for the Sensor. Or maybe you want to have the DataSource sum the list of values and return the result. That lets you keep the return type as a single ValueType value, instead of a std::vector.)

When taking another Accumulator as a DataSource, the "parent" Accumulator would compute a frequency for itself as a multiple of the child Accumulator's own interval, and reads that many values from the DataSource every interval. So if a 10-minute-interval Accumulator is pulling from a 1-minute-interval Accumulator, its frequency is 10 and when it's triggered, it calls DataSource.getValues(10) on its DataSource. Pulling from a 2-minute-interval Accumulator, it would call .getValues(5) once every 10 minutes.

If you keep a master std::vector of Accumulators and sort it so the shortest-interval ones always update first, then they'll have data ready for their parents whenever they're polled.

This breaks the contention by some that push is better than pull, but it better expresses the relationship between the Accumulators in the hierarchy. Each Accumulator can only have one DataSource and only has to deal with retrieving data from that source. A given Accumulator may be polled by multiple parents (think 2-, 3-, and 7-minute-interval Accumulators, all reading from the same 1-minute-interval DataSource), and the child Accumulator doesn't have to know or care about that complexity.

FeRD
  • 1,699
  • 15
  • 24