Skip to content

Commit 722c6a5

Browse files
committed
fix: better default timestamp generating (#69)
1 parent 66ed5d0 commit 722c6a5

File tree

8 files changed

+124
-43
lines changed

8 files changed

+124
-43
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Changelog
2+
3+
## Version 3.3.0 (in progress)
4+
- [FIX] More precice default timestamp generating, up to microseconds
5+
26
## Version 3.2.0 (2020-06-09)
37
- [NEW] Added possibility to read data from InfluxDB using Flux queries
48
- [NEW] `timeSync` utility function for synchronous time synchronization using NTP

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ InfluxDB client for Arduino can write data in batches. A batch is simply a set o
153153
If using batch writes, the timestamp should be employed. Timestamp specifies the time where data was gathered and it is used in the form of a number of seconds (milliseconds, etc) from epoch (1.1.1970) UTC.
154154
If points have no timestamp assigned, InfluxDB assigns timestamp at the time of writing, which could happen much later than the data has been obtained, because final batch write will happen when the batch is full (or when [flush buffer](#buffer-handling-and-retrying) is forced).
155155
156-
InfuxDB allows sending timestamp in various precisions - nanoseconds, microseconds, milliseconds or seconds. The milliseconds precision is usually enough for using on Arduino.
156+
InfuxDB allows sending timestamp in various precisions - nanoseconds, microseconds, milliseconds or seconds. The milliseconds precision is usually enough for using on Arduino. Maximum avavailable precision is microseconds. Setting to nanosecond will just add zeroes for microseconds fraction.
157157
158158
The client has to be configured with time precision. The default settings is not using the timestamp. The `setWriteOptions` functions allow setting various parameters and one of them is __write precision__:
159159
``` cpp
@@ -164,8 +164,10 @@ When a write precision is configured, the client will automatically assign curre
164164

165165
If you want to manage timestamp on your own, there are several ways how to set timestamp explicitly.
166166
- `setTime(WritePrecision writePrecision)` - Sets timestamp to actual time in desired precision
167-
- `setTime(unsigned long seconds)` - Sets timestamp in seconds since epoch. Write precision must be set to `S`
168-
- `setTime(String timestamp)` - Set custom timestamp in precision specified in InfluxDBClient.
167+
- `setTime(unsigned long long timestamp)` - Sets timestamp in an offset since epoch. Correct precision must be set InfluxDBClient::setWriteOptions.
168+
- `setTime(String timestamp)` - Sets timestamp in an offset since epoch. Correct precision must be set InfluxDBClient::setWriteOptions.
169+
170+
The `getTime()` method allows copying timestamp between points.
169171

170172

171173
### Configure Time

src/InfluxDbClient.cpp

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -115,32 +115,30 @@ String Point::toLineProtocol() const {
115115
}
116116

117117
void Point::setTime(WritePrecision precision) {
118-
static char buff[10];
119-
time_t now = time(nullptr);
118+
struct timeval tv;
119+
gettimeofday(&tv, NULL);
120+
120121
switch(precision) {
121122
case WritePrecision::NS:
122-
sprintf(buff, "%06d000", micros()%1000000uL);
123-
_timestamp = String(now) + buff;
123+
setTime(getTimeStamp(&tv,9));
124124
break;
125125
case WritePrecision::US:
126-
sprintf(buff, "%06d", micros()%1000000uL);
127-
_timestamp = String(now) + buff;
126+
setTime(getTimeStamp(&tv,6));
128127
break;
129-
case WritePrecision::MS:
130-
sprintf(buff, "%03d", millis()%1000u);
131-
_timestamp = String(now) + buff;
128+
case WritePrecision::MS:
129+
setTime(getTimeStamp(&tv,3));
130+
break;
131+
case WritePrecision::S:
132+
setTime(getTimeStamp(&tv,0));
132133
break;
133134
case WritePrecision::NoTime:
134135
_timestamp = "";
135136
break;
136-
case WritePrecision::S:
137-
_timestamp = String(now);
138-
break;
139137
}
140138
}
141139

142-
void Point::setTime(unsigned long timestamp) {
143-
_timestamp = String(timestamp);
140+
void Point::setTime(unsigned long long timestamp) {
141+
_timestamp = timeStampToString(timestamp);
144142
}
145143

146144
void Point::setTime(String timestamp) {

src/InfluxDbClient.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ class Point {
8383
void addField(String name, const char *value);
8484
// Set timestamp to `now()` and store it in specified precision, nanoseconds by default. Date and time must be already set. See `configTime` in the device API
8585
void setTime(WritePrecision writePrecision = WritePrecision::NS);
86-
// Set timestamp in seconds since epoch (1.1.1970). Precision should be set to `S`
87-
void setTime(unsigned long seconds);
88-
// Set timestamp in desired precision (specified in InfluxDBClient) since epoch (1.1.1970 00:00:00)
86+
// Set timestamp in offset since epoch (1.1.1970). Correct precision must be set InfluxDBClient::setWriteOptions.
87+
void setTime(unsigned long long timestamp);
88+
// Set timestamp in offset since epoch (1.1.1970 00:00:00). Correct precision must be set InfluxDBClient::setWriteOptions.
8989
void setTime(String timestamp);
9090
// Clear all fields. Usefull for reusing point
9191
void clearFields();
@@ -95,10 +95,12 @@ class Point {
9595
bool hasFields() const { return _fields.length() > 0; }
9696
// True if a point contains at least one tag
9797
bool hasTags() const { return _tags.length() > 0; }
98-
// True if a point contains timestamp
98+
// True if a point contains timestamp
9999
bool hasTime() const { return _timestamp.length() > 0; }
100100
// Creates line protocol
101101
String toLineProtocol() const;
102+
// returns current timestamp
103+
String getTime() const { return _timestamp; }
102104
protected:
103105
String _measurement;
104106
String _tags;

src/util/helpers.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,31 @@ void timeSync(const char *tzInfo, const char* ntpServer1, const char* ntpServer2
4545
time_t tnow = time(nullptr);
4646
Serial.print("Synchronized time: ");
4747
Serial.println(ctime(&tnow));
48+
}
49+
50+
unsigned long long getTimeStamp(struct timeval *tv, int secFracDigits) {
51+
unsigned long long tsVal = 0;
52+
switch(secFracDigits) {
53+
case 0:
54+
tsVal = tv->tv_sec;
55+
break;
56+
case 6:
57+
tsVal = tv->tv_sec * 1000000LL + tv->tv_usec;
58+
break;
59+
case 9:
60+
tsVal = tv->tv_sec * 1000000000LL + tv->tv_usec * 1000LL;
61+
break;
62+
case 3:
63+
default:
64+
tsVal = tv->tv_sec * 1000LL + tv->tv_usec / 1000LL;
65+
break;
66+
67+
}
68+
return tsVal;
69+
}
70+
71+
String timeStampToString(unsigned long long timestamp) {
72+
static char buff[50];
73+
snprintf(buff, 50, "%llu", timestamp);
74+
return String(buff);
4875
}

src/util/helpers.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,17 @@
2828
#define _INFLUXDB_CLIENT_HELPERS_H
2929

3030
#include <Arduino.h>
31+
#include <sys/time.h>
3132

3233
// Synchronize time with NTP servers and waits for completition. Prints waiting progress and final synchronized time to the serial.
3334
// Accurate time is necessary for certificate validion and writing points in batch
3435
// For the fastest time sync find NTP servers in your area: https://www.pool.ntp.org/zone/
3536
void timeSync(const char *tzInfo, const char* ntpServer1, const char* ntpServer2 = nullptr, const char* ntpServer3 = nullptr);
3637

38+
// Create timestamp in offset from epoch. secFracDigits specify resulution. 0 - seconds, 3 - milliseconds, etc. Maximum and default is 9 - nanoseconds.
39+
unsigned long long getTimeStamp(struct timeval *tv, int secFracDigits = 3);
40+
41+
// Converts unsigned long long timestamp to String
42+
String timeStampToString(unsigned long long timestamp);
43+
3744
#endif //_INFLUXDB_CLIENT_HELPERS_H

test/TestSupport.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#ifndef _TEST_SUPPORT_H_
22
#define _TEST_SUPPORT_H_
33

4-
#define TEST_INIT(name) int temp = failures; const char *testName = name; do { Serial.println(testName)
5-
#define TEST_END() } while(0); Serial.printf("%s %s\n",testName,failures == temp?"SUCCEEDED":"FAILED")
6-
#define TEST_ASSERT(a) if(testAssert(__LINE__, (a))) break
7-
#define TEST_ASSERTM(a,m) if(testAssertm(__LINE__, (a),(m))) break
4+
#define TEST_INIT(name) int temp = failures;\
5+
const char *testName = name; \
6+
do { \
7+
Serial.println(testName)
8+
#define TEST_END() } while(0); \
9+
end: Serial.printf("%s %s\n",testName,failures == temp?"SUCCEEDED":"FAILED")
10+
#define TEST_ASSERT(a) if(testAssert(__LINE__, (a))) goto end
11+
#define TEST_ASSERTM(a,m) if(testAssertm(__LINE__, (a),(m))) goto end
812

913
#include "query/FluxParser.h"
1014

test/test.ino

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,10 @@ void setup() {
4848
}
4949

5050
void loop() {
51-
//tests
51+
// tests
5252
testPoint();
5353
testFluxTypes();
5454
testFluxParserEmpty();
55-
5655
testFluxParserSingleTable();
5756
testFluxParserNilValue();
5857
testFluxParserMultiTables(false);
@@ -68,11 +67,11 @@ void loop() {
6867
testUserAgent();
6968
testFailedWrites();
7069
testTimestamp();
71-
// testRetryOnFailedConnection();
72-
// testBufferOverwriteBatchsize1();
73-
// testBufferOverwriteBatchsize5();
74-
// testServerTempDownBatchsize5();
75-
// testRetriesOnServerOverload();
70+
testRetryOnFailedConnection();
71+
testBufferOverwriteBatchsize1();
72+
testBufferOverwriteBatchsize5();
73+
testServerTempDownBatchsize5();
74+
testRetriesOnServerOverload();
7675

7776
Serial.printf("Test %s\n", failures ? "FAILED" : "SUCCEEDED");
7877
while(1) delay(1000);
@@ -115,15 +114,22 @@ void testPoint() {
115114

116115
TEST_ASSERT(!p.hasTime());
117116
time_t now = time(nullptr);
117+
String snow(now);
118118
p.setTime(now);
119-
String testLineTime = testLine + " " + now;
119+
String testLineTime = testLine + " " + snow;
120+
line = p.toLineProtocol();
121+
TEST_ASSERTM(line == testLineTime, line);
122+
123+
unsigned long long ts = now*1000000000LL+123456789;
124+
p.setTime(ts);
125+
testLineTime = testLine + " " + snow + "123456789";
120126
line = p.toLineProtocol();
121127
TEST_ASSERTM(line == testLineTime, line);
122128

123129
now += 10;
124-
String nowStr(now);
125-
p.setTime(nowStr);
126-
testLineTime = testLine + " " + nowStr;
130+
snow = now;
131+
p.setTime(snow);
132+
testLineTime = testLine + " " + snow;
127133
line = p.toLineProtocol();
128134
TEST_ASSERTM(line == testLineTime, line);
129135

@@ -132,29 +138,29 @@ void testPoint() {
132138
int partsCount;
133139
String *parts = getParts(line, ' ', partsCount);
134140
TEST_ASSERTM(partsCount == 3, String("3 != ") + partsCount);
135-
TEST_ASSERT(parts[2].length() == nowStr.length());
141+
TEST_ASSERT(parts[2].length() == snow.length());
136142
delete[] parts;
137143

138144
p.setTime(WritePrecision::MS);
139145
TEST_ASSERT(p.hasTime());
140146
line = p.toLineProtocol();
141147
parts = getParts(line, ' ', partsCount);
142148
TEST_ASSERT(partsCount == 3);
143-
TEST_ASSERT(parts[2].length() == nowStr.length() + 3);
149+
TEST_ASSERT(parts[2].length() == snow.length() + 3);
144150
delete[] parts;
145151

146152
p.setTime(WritePrecision::US);
147153
line = p.toLineProtocol();
148154
parts = getParts(line, ' ', partsCount);
149155
TEST_ASSERT(partsCount == 3);
150-
TEST_ASSERT(parts[2].length() == nowStr.length() + 6);
156+
TEST_ASSERT(parts[2].length() == snow.length() + 6);
151157
delete[] parts;
152158

153159
p.setTime(WritePrecision::NS);
154160
line = p.toLineProtocol();
155161
parts = getParts(line, ' ', partsCount);
156162
TEST_ASSERT(partsCount == 3);
157-
TEST_ASSERT(parts[2].length() == nowStr.length() + 9);
163+
TEST_ASSERT(parts[2].length() == snow.length() + 9);
158164
delete[] parts;
159165

160166
p.clearFields();
@@ -719,6 +725,31 @@ void testFailedWrites() {
719725

720726
void testTimestamp() {
721727
TEST_INIT("testTimestamp");
728+
729+
struct timeval tv;
730+
tv.tv_usec = 1234;
731+
tv.tv_sec = 5678;
732+
unsigned long long ts = getTimeStamp(&tv, 0);
733+
TEST_ASSERTM( ts == 5678, timeStampToString(ts));
734+
ts = getTimeStamp(&tv, 3);
735+
TEST_ASSERTM( ts == 5678001, timeStampToString(ts));
736+
ts = getTimeStamp(&tv, 6);
737+
TEST_ASSERTM( ts == 5678001234, timeStampToString(ts));
738+
ts = getTimeStamp(&tv, 9);
739+
TEST_ASSERTM( ts == 5678001234000, timeStampToString(ts));
740+
741+
// Test increasing timestamp
742+
String prev = "";
743+
for(int i = 0;i<2000;i++) {
744+
Point p("test");
745+
p.setTime(WritePrecision::US);
746+
String act = p.getTime();
747+
TEST_ASSERTM( i == 0 || prev < act, String(i) + ": " + prev + " vs " + act);
748+
prev = act;
749+
delayMicroseconds(100);
750+
}
751+
752+
722753
serverLog(INFLUXDB_CLIENT_TESTING_URL, "testTimestamp");
723754
InfluxDBClient client(INFLUXDB_CLIENT_TESTING_URL, INFLUXDB_CLIENT_TESTING_ORG, INFLUXDB_CLIENT_TESTING_BUC, INFLUXDB_CLIENT_TESTING_TOK);
724755
client.setWriteOptions(WritePrecision::S, 1, 5);
@@ -977,6 +1008,7 @@ bool testFluxDateTimeValue(FluxQueryResult flux, int columnIndex, const char *c
9771008
TEST_ASSERTM(dt.microseconds == us, String(dt.microseconds) + " vs " + String(us));
9781009
return true;
9791010
} while(0);
1011+
end:
9801012
return false;
9811013
}
9821014

@@ -987,6 +1019,7 @@ bool testStringValue(FluxQueryResult flux, int columnIndex, const char *columnN
9871019
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
9881020
return true;
9891021
} while(0);
1022+
end:
9901023
return false;
9911024
}
9921025

@@ -997,12 +1030,12 @@ bool testStringVector(std::vector<String> vect, const char *values[], int size)
9971030
if(vect[i] != values[i]) {
9981031
Serial.print("assert failure: ");
9991032
Serial.println(vect[i]);
1000-
goto fail;
1033+
goto end;
10011034
}
10021035
}
10031036
return true;
10041037
} while(0);
1005-
fail:
1038+
end:
10061039
return false;
10071040
}
10081041

@@ -1013,6 +1046,7 @@ bool testDoubleValue(FluxQueryResult flux, int columnIndex, const char *columnN
10131046
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
10141047
return true;
10151048
} while(0);
1049+
end:
10161050
return false;
10171051
}
10181052

@@ -1023,6 +1057,7 @@ bool testLongValue(FluxQueryResult flux, int columnIndex, const char *columnNam
10231057
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
10241058
return true;
10251059
} while(0);
1060+
end:
10261061
return false;
10271062
}
10281063

@@ -1033,6 +1068,7 @@ bool testUnsignedLongValue(FluxQueryResult flux, int columnIndex, const char *c
10331068
TEST_ASSERTM(flux.getValueByName(columnName).getRawValue() == rawValue, flux.getValueByName(columnName).getRawValue());
10341069
return true;
10351070
} while(0);
1071+
end:
10361072
return false;
10371073
}
10381074

@@ -1046,6 +1082,7 @@ bool testTableColumns(FluxQueryResult flux, const char *columns[], int columnsC
10461082
TEST_ASSERTM(flux.getColumnIndex("x") == -1, "flux.getColumnIndex(\"x\")");
10471083
return true;
10481084
} while(0);
1085+
end:
10491086
return false;
10501087
}
10511088

@@ -1460,7 +1497,7 @@ void initInet() {
14601497
} else {
14611498
Serial.printf("Connected to: %s (%d)\n", WiFi.SSID().c_str(), WiFi.RSSI());
14621499

1463-
timeSync("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org", "0.cz.pool.ntp.org", "1.cz.pool.ntp.org");
1500+
timeSync("CET-1CEST,M3.5.0,M10.5.0/3", "0.cz.pool.ntp.org", "1.cz.pool.ntp.org", "pool.ntp.org");
14641501

14651502
deleteAll(INFLUXDB_CLIENT_TESTING_URL);
14661503
}

0 commit comments

Comments
 (0)