1
+ exports . id = 'analytics' ;
2
+ exports . title = 'Analytics' ;
3
+ exports . version = '1.0.0' ;
4
+ exports . author = 'Peter Širka' ;
5
+ exports . group = 'Databases' ;
6
+ exports . color = '#D770AD' ;
7
+ exports . input = true ;
8
+ exports . output = 1 ;
9
+ exports . options = { fn : 'next(value.temperature);' , format : '{0} °C' , decimals : 2 } ;
10
+ exports . readme = `# Analytics
11
+
12
+ Creates analytics automatically according a value. The value must be a number. The output is \`Object\`:
13
+
14
+ \`\`\`javascript
15
+ {
16
+ count: 2, // {Number} count of analyzed values in the hour
17
+ decimals: 0, // {Number} count of decimals
18
+ format: '{0} °C', // {String} custom defined format, "{0}" will be a value
19
+ period: 'hourly' // {String} period "hourly" or "daily"
20
+ previous: 15, // {Number} previous calculated value
21
+ raw: 32.3 // {Number} last raw value
22
+ type: 'max', // {String} type of analytics
23
+ value: 32.3, // {Number} last calculated value
24
+ }
25
+ \`\`\`` ;
26
+
27
+ exports . html = `<div class="padding">
28
+ <div data-jc="dropdown" data-jc-path="type" class="m" data-options=";@(Hourly: Sum values)|sum;@(Hourly: A maximum value)|max;@(Hourly: A minimum value)|min;@(Hourly: An average value)|avg;@(Hourly: An average (median) value)|median;@(Daily: Sum values)|Dsum;@(Daily: A maximum value)|Dmax;@(Daily: A minimum value)|Dmin;@(Daily: An average value)|Davg;@(Daily: An average (median) value)|Dmedian" data-required="true">@(Type)</div>
29
+ <div data-jc="codemirror" data-jc-path="fn" data-type="javascript" class="m">@(Analyzator)</div>
30
+ <div class="row">
31
+ <div class="col-md-3 m">
32
+ <div data-jc="textbox" data-jc-path="format" data-placeholder="@(e.g. {0} °C)" data-maxlength="10" data-align="center">@(Format)</div>
33
+ </div>
34
+ <div class="col-md-3 m">
35
+ <div data-jc="textbox" data-jc-path="decimals" data-maxlength="10" data-align="center" data-increment="true" data-jc-type="number">@(Decimals)</div>
36
+ </div>
37
+ </div>
38
+ </div>` ;
39
+
40
+ exports . install = function ( instance ) {
41
+
42
+ const Fs = require ( 'fs' ) ;
43
+ const DOC = { } ;
44
+
45
+ var fn = null ;
46
+ var dbname = 'analytics_' + instance . id ;
47
+ var temporary = F . path . databases ( dbname + '.json' ) ;
48
+ var cache = { } ;
49
+ var current = { } ;
50
+
51
+ cache . datetime = F . datetime ;
52
+ cache . count = 0 ;
53
+ cache . avg = { count : 0 , sum : 0 } ;
54
+ cache . number = 0 ;
55
+ cache . raw = 0 ;
56
+ cache . median = [ ] ;
57
+
58
+ instance . on ( 'close' , function ( ) {
59
+ Fs . unlink ( temporary , NOOP ) ;
60
+ } ) ;
61
+
62
+ instance . on ( 'data' , function ( response ) {
63
+ fn && fn ( response . data , function ( err , value ) {
64
+
65
+ if ( err || value == null )
66
+ return ;
67
+
68
+ var type = typeof ( value ) ;
69
+ if ( type === 'string' ) {
70
+ value = value . parseFloat2 ( ) ;
71
+ type = 'number' ;
72
+ }
73
+
74
+ if ( isNaN ( value ) )
75
+ return ;
76
+
77
+ cache . count ++ ;
78
+ cache . raw = value ;
79
+
80
+ switch ( instance . options . type ) {
81
+ case 'max' :
82
+ case 'Dmax' :
83
+ cache . number = cache . number === null ? value : Math . max ( cache . number , value ) ;
84
+ break ;
85
+ case 'min' :
86
+ case 'Dmin' :
87
+ cache . number = cache . number === null ? value : Math . min ( cache . number , value ) ;
88
+ break ;
89
+ case 'sum' :
90
+ case 'Dsum' :
91
+ cache . number = cache . number === null ? value : cache . number + value ;
92
+ break ;
93
+ case 'avg' :
94
+ case 'Davg' :
95
+ cache . avg . count ++ ;
96
+ cache . avg . sum += value ;
97
+ cache . number = cache . avg . sum / cache . avg . count ;
98
+ break ;
99
+ case 'median' :
100
+ case 'Dmedian' :
101
+ cache . median . push ( value ) ;
102
+ cache . median . sort ( ( a , b ) => a - b ) ;
103
+ var half = Math . floor ( cache . median . length / 2 ) ;
104
+ cache . number = cache . median . length % 2 ? cache . median [ half ] : ( cache . median [ half - 1 ] + cache . median [ half ] ) / 2.0 ;
105
+ break ;
106
+ }
107
+
108
+ current . previous = current . value ;
109
+ current . value = cache . number ;
110
+ current . raw = cache . raw ;
111
+ current . format = instance . options . format ;
112
+ current . type = instance . options . type [ 0 ] === 'D' ? instance . options . type . substring ( 1 ) : instance . options . type ;
113
+ current . count = cache . count ;
114
+ current . period = instance . options . type [ 0 ] === 'D' ? 'daily' : 'hourly' ;
115
+ current . decimals = instance . options . decimals ;
116
+ current . datetime = F . datetime ;
117
+ instance . connections && instance . send ( current ) ;
118
+ instance . custom . status ( ) ;
119
+ EMIT ( 'flow.analytics' , instance , current ) ;
120
+ } ) ;
121
+ } ) ;
122
+
123
+ instance . on ( 'service' , function ( ) {
124
+ if ( fn ) {
125
+ instance . custom . save ( ) ;
126
+ instance . custom . save_temporary ( ) ;
127
+ }
128
+ } ) ;
129
+
130
+ instance . custom . status = function ( ) {
131
+ var number = cache . number ;
132
+ if ( number == null )
133
+ return ;
134
+ number = number . format ( instance . options . decimals || 0 ) ;
135
+ instance . status ( cache . format ? cache . format . format ( number ) : number ) ;
136
+ } ;
137
+
138
+ instance . custom . save_temporary = function ( ) {
139
+ Fs . writeFile ( temporary , JSON . stringify ( cache ) , NOOP ) ;
140
+ } ;
141
+
142
+ instance . custom . current = function ( ) {
143
+ return current ;
144
+ } ;
145
+
146
+ instance . custom . save = function ( ) {
147
+
148
+ if ( instance . options . type [ 0 ] === 'D' ) {
149
+ if ( cache . datetime . getDate ( ) === F . datetime . getDate ( ) )
150
+ return ;
151
+ } else {
152
+ if ( cache . datetime . getHours ( ) === F . datetime . getHours ( ) )
153
+ return ;
154
+ }
155
+
156
+ DOC . id = + cache . datetime . format ( 'yyyyMMddHH' ) ;
157
+ DOC . year = cache . datetime . getFullYear ( ) ;
158
+ DOC . month = cache . datetime . getMonth ( ) + 1 ;
159
+ DOC . day = cache . datetime . getDate ( ) ;
160
+ DOC . hour = cache . datetime . getHours ( ) ;
161
+ DOC . week = + cache . datetime . format ( 'w' ) ;
162
+ DOC . count = cache . count ;
163
+ DOC . value = cache . number ;
164
+ DOC . type = cache . type [ 0 ] === 'D' ? cache . type . substring ( 1 ) : cache . type ;
165
+ DOC . period = cache . type [ 0 ] === 'D' ? 'daily' : 'hourly' ;
166
+ DOC . format = cache . format ;
167
+ DOC . datecreated = F . datetime ;
168
+
169
+ NOSQL ( dbname ) . update ( DOC , DOC ) . where ( 'id' , DOC . id ) ;
170
+
171
+ cache . count = 0 ;
172
+ cache . number = null ;
173
+
174
+ switch ( instance . options . type ) {
175
+ case 'median' :
176
+ case 'Dmedian' :
177
+ cache . median = [ ] ;
178
+ break ;
179
+ case 'avg' :
180
+ case 'Davg' :
181
+ cache . avg . count = 0 ;
182
+ cache . avg . sum = 0 ;
183
+ break ;
184
+ }
185
+
186
+ cache . datetime = F . datetime ;
187
+ } ;
188
+
189
+ instance . custom . reconfigure = function ( ) {
190
+ var options = instance . options ;
191
+
192
+ if ( ! options . type ) {
193
+ instance . status ( 'Not configured' , 'red' ) ;
194
+ fn = null ;
195
+ return ;
196
+ }
197
+
198
+ cache . type = options . type ;
199
+ cache . format = options . format ;
200
+
201
+ fn = SCRIPT ( options . fn ) ;
202
+ instance . custom . status ( ) ;
203
+ } ;
204
+
205
+ instance . on ( 'options' , instance . custom . reconfigure ) ;
206
+ instance . custom . stats = ( callback ) => callback ( null , NOSQL ( dbname ) ) ;
207
+ instance . custom . reconfigure ( ) ;
208
+
209
+ Fs . readFile ( temporary , function ( err , data ) {
210
+ if ( err )
211
+ return ;
212
+ var dt = cache . datetime || F . datetime ;
213
+ var tmp = data . toString ( 'utf8' ) . parseJSON ( true ) ;
214
+ if ( tmp && tmp . datetime ) {
215
+ cache = tmp ;
216
+ if ( cache . type [ 0 ] === 'D' )
217
+ cache . datetime . getDate ( ) !== dt . getDate ( ) && instance . custom . save ( ) ;
218
+ else
219
+ cache . datetime . getHours ( ) !== dt . getHours ( ) && instance . custom . save ( ) ;
220
+ instance . custom . status ( ) ;
221
+ }
222
+ } ) ;
223
+ } ;
0 commit comments