@@ -20,6 +20,7 @@ import (
20
20
"encoding/json"
21
21
"errors"
22
22
"fmt"
23
+ "slices"
23
24
"strconv"
24
25
"strings"
25
26
"sync"
@@ -95,10 +96,10 @@ func (a *TsExtractor) ExportTSToFile(
95
96
} else {
96
97
a .logger .Infoln ("=====> Exporting data. Time window: " , timeWindowInMinutes , "m (resolution: " , resolution , "s). From " , from , " to " , to , " - aggregation: " , aggregationStat )
97
98
}
98
- for thingID , thing := range thingsMap {
99
+ for _ , thing := range thingsMap {
99
100
100
101
if len (thing .Properties ) == 0 {
101
- a .logger .Warn ("Skipping thing with no properties: " , thingID )
102
+ a .logger .Warn ("Skipping thing with no properties: " , thing . Id )
102
103
continue
103
104
}
104
105
@@ -109,31 +110,51 @@ func (a *TsExtractor) ExportTSToFile(
109
110
defer func () { <- tokens }()
110
111
defer wg .Done ()
111
112
112
- if isRawResolution (resolution ) {
113
+ detectedProperties := []string {}
114
+ isRaw := isRawResolution (resolution )
115
+ if isRaw {
113
116
// Populate raw time series data
114
- err := a .populateRawTSDataIntoS3 (ctx , from , to , thing , writer )
117
+ populatedProperties , err := a .populateRawTSDataIntoS3 (ctx , from , to , thing , writer )
115
118
if err != nil {
116
119
a .logger .Error ("Error populating raw time series data: " , err )
117
120
errorChannel <- err
118
121
return
119
122
}
123
+ if len (populatedProperties ) > 0 {
124
+ detectedProperties = append (detectedProperties , populatedProperties ... )
125
+ }
120
126
} else {
121
127
// Populate numeric time series data
122
- err := a .populateNumericTSDataIntoS3 (ctx , from , to , thing , resolution , aggregationStat , writer )
128
+ populatedProperties , err := a .populateNumericTSDataIntoS3 (ctx , from , to , thing , resolution , aggregationStat , writer )
123
129
if err != nil {
124
130
a .logger .Error ("Error populating time series data: " , err )
125
131
errorChannel <- err
126
132
return
127
133
}
134
+ if len (populatedProperties ) > 0 {
135
+ detectedProperties = append (detectedProperties , populatedProperties ... )
136
+ }
128
137
129
138
// Populate string time series data, if any
130
- err = a .populateStringTSDataIntoS3 (ctx , from , to , thing , resolution , writer )
139
+ populatedProperties , err = a .populateStringTSDataIntoS3 (ctx , from , to , thing , resolution , writer )
131
140
if err != nil {
132
141
a .logger .Error ("Error populating string time series data: " , err )
133
142
errorChannel <- err
134
143
return
135
144
}
145
+ if len (populatedProperties ) > 0 {
146
+ detectedProperties = append (detectedProperties , populatedProperties ... )
147
+ }
136
148
}
149
+
150
+ // Populate last value samples for ON_CHANGE properties, if needed
151
+ err = a .populateLastValueSamplesForOnChangeProperties (isRaw , thing , detectedProperties , writer )
152
+ if err != nil {
153
+ a .logger .Error ("Error populating last value data: " , err )
154
+ errorChannel <- err
155
+ return
156
+ }
157
+
137
158
}(thing , writer )
138
159
}
139
160
@@ -174,12 +195,13 @@ func (a *TsExtractor) populateNumericTSDataIntoS3(
174
195
thing iotclient.ArduinoThing ,
175
196
resolution int ,
176
197
aggregationStat string ,
177
- writer * csv.CsvWriter ) error {
198
+ writer * csv.CsvWriter ) ([] string , error ) {
178
199
179
200
if resolution <= 60 {
180
201
resolution = 60
181
202
}
182
203
204
+ populatedProperties := []string {}
183
205
var batched * iotclient.ArduinoSeriesBatch
184
206
var err error
185
207
var retry bool
@@ -194,7 +216,7 @@ func (a *TsExtractor) populateNumericTSDataIntoS3(
194
216
}
195
217
}
196
218
if err != nil {
197
- return err
219
+ return nil , err
198
220
}
199
221
200
222
sampleCount := int64 (0 )
@@ -214,19 +236,22 @@ func (a *TsExtractor) populateNumericTSDataIntoS3(
214
236
215
237
ts := response .Times [i ]
216
238
value := response .Values [i ]
239
+ if ! slices .Contains (populatedProperties , propertyID ) {
240
+ populatedProperties = append (populatedProperties , propertyID )
241
+ }
217
242
samples = append (samples , composeRow (ts , thing .Id , thing .Name , propertyID , propertyName , propertyType , strconv .FormatFloat (value , 'f' , - 1 , 64 ), aggregationStat ))
218
243
}
219
244
}
220
245
221
246
// Write samples to csv ouput file
222
247
if len (samples ) > 0 {
223
248
if err := writer .Write (samples ); err != nil {
224
- return err
249
+ return nil , err
225
250
}
226
251
a .logger .Debugf ("Thing %s [%s] saved %d values\n " , thing .Id , thing .Name , sampleCount )
227
252
}
228
253
229
- return nil
254
+ return populatedProperties , nil
230
255
}
231
256
232
257
func composeRow (ts time.Time , thingID string , thingName string , propertyID string , propertyName string , propertyType string , value string , aggregation string ) []string {
@@ -280,7 +305,7 @@ func (a *TsExtractor) populateStringTSDataIntoS3(
280
305
to time.Time ,
281
306
thing iotclient.ArduinoThing ,
282
307
resolution int ,
283
- writer * csv.CsvWriter ) error {
308
+ writer * csv.CsvWriter ) ([] string , error ) {
284
309
285
310
// Filter properties by char type
286
311
stringProperties := []string {}
@@ -291,9 +316,10 @@ func (a *TsExtractor) populateStringTSDataIntoS3(
291
316
}
292
317
293
318
if len (stringProperties ) == 0 {
294
- return nil
319
+ return nil , nil
295
320
}
296
321
322
+ populatedProperties := []string {}
297
323
var batched * iotclient.ArduinoSeriesBatchSampled
298
324
var err error
299
325
var retry bool
@@ -308,7 +334,7 @@ func (a *TsExtractor) populateStringTSDataIntoS3(
308
334
}
309
335
}
310
336
if err != nil {
311
- return err
337
+ return nil , err
312
338
}
313
339
314
340
sampleCount := int64 (0 )
@@ -331,28 +357,32 @@ func (a *TsExtractor) populateStringTSDataIntoS3(
331
357
if value == nil {
332
358
continue
333
359
}
360
+ if ! slices .Contains (populatedProperties , propertyID ) {
361
+ populatedProperties = append (populatedProperties , propertyID )
362
+ }
334
363
samples = append (samples , composeRow (ts , thing .Id , thing .Name , propertyID , propertyName , propertyType , a .interfaceToString (value ), "" ))
335
364
}
336
365
}
337
366
338
367
// Write samples to csv ouput file
339
368
if len (samples ) > 0 {
340
369
if err := writer .Write (samples ); err != nil {
341
- return err
370
+ return nil , err
342
371
}
343
372
a .logger .Debugf ("Thing %s [%s] string properties saved %d values\n " , thing .Id , thing .Name , sampleCount )
344
373
}
345
374
346
- return nil
375
+ return populatedProperties , nil
347
376
}
348
377
349
378
func (a * TsExtractor ) populateRawTSDataIntoS3 (
350
379
ctx context.Context ,
351
380
from time.Time ,
352
381
to time.Time ,
353
382
thing iotclient.ArduinoThing ,
354
- writer * csv.CsvWriter ) error {
383
+ writer * csv.CsvWriter ) ([] string , error ) {
355
384
385
+ populatedProperties := []string {}
356
386
var batched * iotclient.ArduinoSeriesRawBatch
357
387
var err error
358
388
var retry bool
@@ -367,7 +397,7 @@ func (a *TsExtractor) populateRawTSDataIntoS3(
367
397
}
368
398
}
369
399
if err != nil {
370
- return err
400
+ return nil , err
371
401
}
372
402
373
403
sampleCount := int64 (0 )
@@ -390,19 +420,22 @@ func (a *TsExtractor) populateRawTSDataIntoS3(
390
420
if value == nil {
391
421
continue
392
422
}
423
+ if ! slices .Contains (populatedProperties , propertyID ) {
424
+ populatedProperties = append (populatedProperties , propertyID )
425
+ }
393
426
samples = append (samples , composeRawRow (ts , thing .Id , thing .Name , propertyID , propertyName , propertyType , a .interfaceToString (value )))
394
427
}
395
428
}
396
429
397
430
// Write samples to csv ouput file
398
431
if len (samples ) > 0 {
399
432
if err := writer .Write (samples ); err != nil {
400
- return err
433
+ return nil , err
401
434
}
402
435
a .logger .Debugf ("Thing %s [%s] raw data saved %d values\n " , thing .Id , thing .Name , sampleCount )
403
436
}
404
437
405
- return nil
438
+ return populatedProperties , nil
406
439
}
407
440
408
441
func (a * TsExtractor ) interfaceToString (value interface {}) string {
@@ -426,3 +459,42 @@ func (a *TsExtractor) interfaceToString(value interface{}) string {
426
459
return fmt .Sprintf ("%v" , v )
427
460
}
428
461
}
462
+
463
+ func (a * TsExtractor ) populateLastValueSamplesForOnChangeProperties (
464
+ isRaw bool ,
465
+ thing iotclient.ArduinoThing ,
466
+ propertiesWithExtractedValue []string ,
467
+ writer * csv.CsvWriter ) error {
468
+
469
+ // Check if there are ON_CHANGE properties
470
+ if len (thing .Properties ) == 0 {
471
+ return nil
472
+ }
473
+ samples := [][]string {}
474
+ sampleCount := 0
475
+ for _ , prop := range thing .Properties {
476
+ if prop .UpdateStrategy == "ON_CHANGE" && ! slices .Contains (propertiesWithExtractedValue , prop .Id ) {
477
+ if prop .ValueUpdatedAt == nil {
478
+ continue
479
+ }
480
+ var toAdd []string
481
+ if isRaw {
482
+ toAdd = composeRawRow (* prop .ValueUpdatedAt , thing .Id , thing .Name , prop .Id , prop .Name , prop .Type , a .interfaceToString (prop .LastValue ))
483
+ } else {
484
+ toAdd = composeRow (* prop .ValueUpdatedAt , thing .Id , thing .Name , prop .Id , prop .Name , prop .Type , a .interfaceToString (prop .LastValue ), "LAST_VALUE" )
485
+ }
486
+ samples = append (samples , toAdd )
487
+ sampleCount ++
488
+ }
489
+ }
490
+
491
+ // Write samples to csv ouput file
492
+ if len (samples ) > 0 {
493
+ if err := writer .Write (samples ); err != nil {
494
+ return err
495
+ }
496
+ a .logger .Debugf ("Thing %s [%s] last value data saved %d values\n " , thing .Id , thing .Name , sampleCount )
497
+ }
498
+
499
+ return nil
500
+ }
0 commit comments