Skip to content

Commit 81a5f48

Browse files
committed
Add averager module
1 parent c32d3bb commit 81a5f48

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed

modules/Averager.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/* Copyright (c) 2020 Gordon Williams. See the file LICENSE for copying permission. */
2+
3+
/** Create a new Series of 'size' elements,
4+
with the given options:
5+
{
6+
scale : optional scale to store data in a Int16Array (default 1)
7+
offset : optional offset to store data in a Int16Array (default 0)
8+
}
9+
*/
10+
function Series(size, options) {
11+
options = options||{};
12+
options.scale = options.scale||1;
13+
options.offset = options.offset||0;
14+
options.format = options.format||Int16Array;
15+
// TODO: store in one array to get better packing?
16+
Object.assign(this,{
17+
options : options,
18+
buckets : new options.format(size), ///< current readings
19+
lastBuckets : new options.format(size), ///< last readings
20+
avr : new options.format(size), ///< average readings
21+
avrCnt : 0, ///< number of readings in average
22+
sum : 0, ///< Current bucket sum
23+
cnt : 0, ///< Current number of readings in bucket
24+
lastBucket : 0, ///< The current bucket number
25+
});
26+
}
27+
Series.prototype.add = function(value, bucket) {
28+
//print(bucket, this.lastBucket);
29+
if (bucket != this.lastBucket) {
30+
this.sum = this.cnt = 0;
31+
if (bucket < this.lastBucket) {
32+
// setup last set of readings
33+
this.lastBuckets.set(this.buckets);
34+
// handle average
35+
if (this.avrCnt==0) {
36+
this.avr.set(this.buckets);
37+
} else {
38+
var n = this.avrCnt;
39+
for (var i=0;i<this.lastBucket;i++)
40+
this.avr[i] = (this.avr[i]*n + this.buckets[i]) / (n+1);
41+
}
42+
this.avrCnt++;
43+
// clear current readings
44+
this.buckets.fill(0);
45+
}
46+
this.lastBucket = bucket;
47+
}
48+
this.sum += (value - this.options.offset) * this.options.scale;
49+
this.cnt ++;
50+
this.buckets[bucket] = Math.round(this.sum/this.cnt);
51+
};
52+
/// Get the current array of values
53+
Series.prototype.getCurrent = function() {
54+
return this.buckets.slice().map(x => (x/this.options.scale)+this.options.offset );
55+
};
56+
/// Get an array for the last series
57+
Series.prototype.getLast = function() {
58+
return this.lastBuckets.slice().map(x => (x/this.options.scale)+this.options.offset );
59+
};
60+
/// Get an array for the average
61+
Series.prototype.getAvr = function() {
62+
return this.avr.slice().map(x => (x/this.options.scale)+this.options.offset );
63+
};
64+
65+
/// Create a new Averager with series for hours, days and months. See Series for options
66+
function Averager(options) {
67+
this.series = {
68+
hours : new Series(24*4, options),
69+
days : new Series(31, options),
70+
months : new Series(12, options),
71+
};
72+
}
73+
/// Add a new value to the averager. If date is not specified, the current date is used
74+
Averager.prototype.add = function(value, date) {
75+
if (date===undefined)
76+
date = new Date();
77+
this.series.hours.add(value, Math.round(date.getHours()*4 + date.getMinutes()/15));
78+
this.series.days.add(value, date.getDate()-1);
79+
this.series.months.add(value, date.getMonth());
80+
};
81+
/// Print all the current data from all series' as JSON
82+
Averager.prototype.print = function() {
83+
print('{');
84+
var last = Object.keys(this.series).pop();
85+
for (var k in this.series) {
86+
print('"'+k+'":{"current":',this.series[k].getCurrent(),',');
87+
print('"avr":',this.series[k].getAvr(),',');
88+
print('"last":',this.series[k].getLast(),k==last?'}}':'},');
89+
}
90+
};
91+
92+
exports.Series = Series;
93+
exports.Averager = Averager;

modules/Averager.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<!--- Copyright (c) 2020 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. -->
2+
Averager Library
3+
==============
4+
5+
<span style="color:red">:warning: **Please view the correctly rendered version of this page at https://www.espruino.com/Averager. Links, lists, videos, search, and other features will not work correctly when viewed on GitHub** :warning:</span>
6+
7+
* KEYWORDS: Module,Modules,Data,Storage,Average,Graph
8+
9+
This library allows you to store data that changes over time in a space-efficient
10+
way, and retrieve it later. It's perfect for creating simple graphs of
11+
values like temperature.
12+
13+
There are two main classes:
14+
15+
### Series
16+
17+
Instantiate this with a size (the number of buckets). You then call `Series.add(value, bucket)`
18+
and the value supplied is kept as an average in that bucket.
19+
20+
The Averager assumes that you move linearly through bucket numbers when adding
21+
values. When you move from a high bucket number to a lower bucket
22+
number (eg if moving from the 31st of a month to the 1st of the next month) it
23+
saves the current set of buckets, and also adds them to the average.
24+
25+
Averager maintains three lists. Assuming the 'day of month example':
26+
27+
* `buckets` - the current set of values (eg. data from this month)
28+
* `last` - the last set of values (eg. complete data from last month)
29+
* `avr` - an average of all values (eg. average for every day of every month)
30+
31+
### Averager
32+
33+
This stores several `Series` instances for common time-based recording:
34+
35+
* `hours` - A bucket every 15 minutes each day
36+
* `days` - A bucket for each day of the month
37+
* `months` - A bucket for each month of the year
38+
39+
Options
40+
-------
41+
42+
When you instantiate `Series` or `Averager` you have an object of options for how
43+
data is stored. This contains:
44+
45+
* `format` : optional type of array (default `Int16Array`)
46+
* `scale` : optional scale to store data in the correct range for the array (default 1)
47+
* `offset` : optional offset to store data correct range for the array (default 0)
48+
49+
For example:
50+
51+
* if you're storing temperature you might want to store it high res, so you could use the default `Int16Array` which stores values from -32768 to 32767, and then scale the temperature by 500, giving you roughly `-65` to `+65` range at 0.002 degree accuracy: `{scale:500}`
52+
* or you might want to save space and store low resolution, so you could use `Int8Array` which stores values from -128 to 127, and then scale the temperature by 2, giving you roughly the same `-64` to `+64` range but at 0.5 degree accuracy, using half the RAM: `{format:Int8Array, scale:2}`
53+
54+
55+
Usage
56+
-----
57+
58+
```
59+
var Averager = require("Averager").Averager;
60+
var temperature = new Averager({scale:100});
61+
62+
setInterval(function() {
63+
// every 10 seconds add new temperature data
64+
temperature.add(E.getTemperature());
65+
}, 10000);
66+
```
67+
68+
You can use this with the [graph](/graph) library, so to plot the data so far today
69+
you could do:
70+
71+
```
72+
require("graph").drawLine(g, temperature.series.hours.getCurrent(), {
73+
axes : true,
74+
gridy : 5,
75+
gridx : 24,
76+
xlabel : x=>(x>>2)+":00"
77+
});
78+
```
79+
80+
Or for a bar graph of the average each month, you could do:
81+
82+
```
83+
require("graph").drawBar(g, temperature.series.months.getAvr(), {
84+
axes : true,
85+
gridy : 5,
86+
gridx : 2,
87+
xlabel : x=>"Jan,Mar,May,Jul,Oct,Nov".split(",")[x/2]
88+
});
89+
```
90+
91+
You can also call `temperature.print()` when accessing Espruino remotely
92+
in order to get a JSON-formatted version of the data, for instance:
93+
94+
```
95+
{
96+
"hours": {
97+
"current": [ 0.452, 0.192, 0.488, 0.576, 0.044, ... 0, 0, 0, 0, 0 ],
98+
"avr": [ 0.196, 0.22, 0.188, 0.176, 0.216, ... 0.188, 0.196, 0.16, 0.188, 0.404 ],
99+
"last": [ 0.092, 0.64, 0.388, 0.316, 0.604, ... 0.36, 0.4, 0.864, 0.676, 0.46 ]
100+
},
101+
"days": {
102+
"current": [ 0.536, 0.516, 0.444, 0.5, 0.488, ... 0, 0, 0, 0, 0 ],
103+
"avr": [ 0.44, 0.448, 0.448, 0.432, 0.46, ... 0.5, 0.484, 0.484, 0.496, 0.536 ],
104+
"last": [ 0.544, 0.448, 0.532, 0.544, 0.588, ... 0.472, 0.512, 0.488, 0.508, 0.464 ]
105+
},
106+
months: {
107+
"current": [ 0.508, 0.5, 0.504, 0.488, 0.5, 0.508, 0.508, 0.5, 0.484, 0, 0, 0 ],
108+
"avr": [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.492 ],
109+
"last": [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.492 ]
110+
}
111+
}
112+
```

modules/Averager.thumb.png

9.72 KB
Loading

0 commit comments

Comments
 (0)